68 changed files with 365 additions and 4528 deletions
@ -1,23 +0,0 @@ |
|||
///
|
|||
/// Copyright © 2016-2024 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
|
|||
import { JsonSettingsSchema } from '@shared/models/widget.models'; |
|||
|
|||
export interface JsonFormComponentData extends JsonSettingsSchema { |
|||
model?: any; |
|||
settingsDirective?: string; |
|||
} |
|||
@ -1,23 +0,0 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2024 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<div class="tb-json-form" style="background: #fff;" tb-fullscreen [fullscreenElement]="reactFullscreen" |
|||
[fullscreen]="isFullscreen" |
|||
(fullscreenChanged)="onFullscreenChanged($event)"> |
|||
<div #reactRoot></div> |
|||
<div class="tb-json-form" #reactFullscreen></div> |
|||
</div> |
|||
@ -1,20 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
.tb-json-form { |
|||
padding: 12px; |
|||
padding-bottom: 14px !important; |
|||
overflow: auto; |
|||
} |
|||
@ -1,293 +0,0 @@ |
|||
///
|
|||
/// Copyright © 2016-2024 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { |
|||
ChangeDetectorRef, |
|||
Component, |
|||
ElementRef, |
|||
forwardRef, |
|||
Input, |
|||
OnChanges, |
|||
OnDestroy, |
|||
Renderer2, |
|||
SimpleChanges, |
|||
ViewChild, ViewContainerRef, |
|||
ViewEncapsulation |
|||
} from '@angular/core'; |
|||
import { ControlValueAccessor, UntypedFormControl, NG_VALIDATORS, NG_VALUE_ACCESSOR, Validator } from '@angular/forms'; |
|||
import { coerceBooleanProperty } from '@angular/cdk/coercion'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '@core/core.state'; |
|||
import { deepClone, isString, unwrapModule } from '@app/core/utils'; |
|||
import { JsonFormProps } from './react/json-form.models'; |
|||
import inspector from 'schema-inspector'; |
|||
import tinycolor from 'tinycolor2'; |
|||
import { DialogService } from '@app/core/services/dialog.service'; |
|||
import JsonFormUtils from './react/json-form-utils'; |
|||
import { JsonFormComponentData } from './json-form-component.models'; |
|||
import { GroupInfo } from '@shared/models/widget.models'; |
|||
import { Observable } from 'rxjs/internal/Observable'; |
|||
import { forkJoin, from } from 'rxjs'; |
|||
import { TbPopoverService } from '@shared/components/popover.service'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-json-form', |
|||
templateUrl: './json-form.component.html', |
|||
styleUrls: ['./json-form.component.scss'], |
|||
providers: [ |
|||
{ |
|||
provide: NG_VALUE_ACCESSOR, |
|||
useExisting: forwardRef(() => JsonFormComponent), |
|||
multi: true |
|||
}, |
|||
{ |
|||
provide: NG_VALIDATORS, |
|||
useExisting: forwardRef(() => JsonFormComponent), |
|||
multi: true, |
|||
} |
|||
], |
|||
encapsulation: ViewEncapsulation.None |
|||
}) |
|||
export class JsonFormComponent implements ControlValueAccessor, Validator, OnChanges, OnDestroy { |
|||
|
|||
@ViewChild('reactRoot', {static: true}) |
|||
reactRootElmRef: ElementRef<HTMLElement>; |
|||
|
|||
@ViewChild('reactFullscreen', {static: true}) |
|||
reactFullscreenElmRef: ElementRef<HTMLElement>; |
|||
|
|||
private readonlyValue: boolean; |
|||
get readonly(): boolean { |
|||
return this.readonlyValue; |
|||
} |
|||
@Input() |
|||
set required(value: boolean) { |
|||
this.readonlyValue = coerceBooleanProperty(value); |
|||
} |
|||
|
|||
formProps: JsonFormProps = { |
|||
isFullscreen: false, |
|||
option: { |
|||
formDefaults: { |
|||
startEmpty: true |
|||
} |
|||
}, |
|||
onModelChange: this.onModelChange.bind(this), |
|||
onColorClick: this.onColorClick.bind(this), |
|||
onIconClick: this.onIconClick.bind(this), |
|||
onToggleFullscreen: this.onToggleFullscreen.bind(this), |
|||
onHelpClick: this.onHelpClick.bind(this) |
|||
}; |
|||
|
|||
data: JsonFormComponentData; |
|||
|
|||
model: any; |
|||
schema: any; |
|||
form: any; |
|||
groupInfoes: GroupInfo[]; |
|||
|
|||
isModelValid = true; |
|||
|
|||
isFullscreen = false; |
|||
fullscreenFinishFn: (el: Element) => void; |
|||
|
|||
private reactRoot: any; |
|||
|
|||
private propagateChange = null; |
|||
private propagateChangePending = false; |
|||
private writingValue = false; |
|||
private updateViewPending = false; |
|||
|
|||
constructor(public elementRef: ElementRef, |
|||
private dialogs: DialogService, |
|||
private popoverService: TbPopoverService, |
|||
private renderer: Renderer2, |
|||
private viewContainerRef: ViewContainerRef, |
|||
protected store: Store<AppState>, |
|||
private cd: ChangeDetectorRef) { |
|||
} |
|||
|
|||
ngOnDestroy(): void { |
|||
this.destroyReactSchemaForm(); |
|||
} |
|||
|
|||
registerOnChange(fn: any): void { |
|||
this.propagateChange = fn; |
|||
if (this.propagateChangePending) { |
|||
this.propagateChangePending = false; |
|||
setTimeout(() => { |
|||
this.propagateChange(this.data); |
|||
}, 0); |
|||
} |
|||
} |
|||
|
|||
registerOnTouched(fn: any): void { |
|||
} |
|||
|
|||
setDisabledState(isDisabled: boolean): void { |
|||
} |
|||
|
|||
public validate(c: UntypedFormControl) { |
|||
return this.isModelValid ? null : { |
|||
modelValid: false |
|||
}; |
|||
} |
|||
|
|||
writeValue(data: JsonFormComponentData): void { |
|||
this.writingValue = true; |
|||
this.data = data; |
|||
this.schema = this.data && this.data.schema ? deepClone(this.data.schema) : { |
|||
type: 'object' |
|||
}; |
|||
this.schema.strict = true; |
|||
this.form = this.data && this.data.form ? deepClone(this.data.form) : [ '*' ]; |
|||
this.groupInfoes = this.data && this.data.groupInfoes ? deepClone(this.data.groupInfoes) : []; |
|||
this.model = this.data && this.data.model || {}; |
|||
this.model = inspector.sanitize(this.schema, this.model).data; |
|||
this.updateAndRender(); |
|||
this.isModelValid = this.validateModel(); |
|||
this.writingValue = false; |
|||
if (!this.isModelValid || this.updateViewPending) { |
|||
this.updateView(); |
|||
} |
|||
} |
|||
|
|||
updateView() { |
|||
if (!this.writingValue) { |
|||
this.updateViewPending = false; |
|||
if (this.data) { |
|||
this.data.model = this.model; |
|||
if (this.propagateChange) { |
|||
try { |
|||
this.propagateChange(this.data); |
|||
} catch (e) { |
|||
this.propagateChangePending = true; |
|||
} |
|||
} else { |
|||
this.propagateChangePending = true; |
|||
} |
|||
} |
|||
} else { |
|||
this.updateViewPending = true; |
|||
} |
|||
} |
|||
|
|||
ngOnChanges(changes: SimpleChanges): void { |
|||
for (const propName of Object.keys(changes)) { |
|||
const change = changes[propName]; |
|||
if (!change.firstChange && change.currentValue !== change.previousValue) { |
|||
if (propName === 'readonly') { |
|||
this.updateAndRender(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private onModelChange(key: (string | number)[], val: any, forceUpdate = false) { |
|||
if (isString(val) && val === '') { |
|||
val = undefined; |
|||
} |
|||
if (JsonFormUtils.updateValue(key, this.model, val) || forceUpdate) { |
|||
this.isModelValid = this.validateModel(); |
|||
this.updateView(); |
|||
} |
|||
} |
|||
|
|||
private onColorClick(key: (string | number)[], |
|||
val: tinycolor.ColorFormats.RGBA, |
|||
colorSelectedFn: (color: tinycolor.ColorFormats.RGBA) => void) { |
|||
this.dialogs.colorPicker(tinycolor(val).toRgbString()).subscribe((result) => { |
|||
if (!result?.canceled && colorSelectedFn) { |
|||
colorSelectedFn(tinycolor(result?.color).toRgb()); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private onIconClick(key: (string | number)[], |
|||
val: string, |
|||
iconSelectedFn: (icon: string) => void) { |
|||
this.dialogs.materialIconPicker(val).subscribe((result) => { |
|||
if (!result?.canceled && iconSelectedFn) { |
|||
iconSelectedFn(result?.icon); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private onToggleFullscreen(fullscreenFinishFn?: (el: Element) => void) { |
|||
this.isFullscreen = !this.isFullscreen; |
|||
this.fullscreenFinishFn = fullscreenFinishFn; |
|||
this.cd.markForCheck(); |
|||
} |
|||
|
|||
onFullscreenChanged(fullscreen: boolean) { |
|||
this.formProps.isFullscreen = fullscreen; |
|||
this.renderReactSchemaForm(false); |
|||
if (this.fullscreenFinishFn) { |
|||
this.fullscreenFinishFn(this.reactFullscreenElmRef.nativeElement); |
|||
this.fullscreenFinishFn = null; |
|||
} |
|||
} |
|||
|
|||
private onHelpClick(event: MouseEvent, helpId: string, helpVisibleFn: (visible: boolean) => void, helpReadyFn: (ready: boolean) => void) { |
|||
const trigger = event.currentTarget as Element; |
|||
this.popoverService.toggleHelpPopover(trigger, this.renderer, this.viewContainerRef, helpId, '', '', null, helpVisibleFn, helpReadyFn); |
|||
} |
|||
|
|||
private updateAndRender() { |
|||
|
|||
this.formProps.option.formDefaults.readonly = this.readonly; |
|||
this.formProps.schema = this.schema; |
|||
this.formProps.form = this.form; |
|||
this.formProps.groupInfoes = this.groupInfoes; |
|||
this.formProps.model = this.model; |
|||
this.renderReactSchemaForm(); |
|||
} |
|||
|
|||
private renderReactSchemaForm(destroy: boolean = true) { |
|||
if (destroy) { |
|||
this.destroyReactSchemaForm(); |
|||
} |
|||
|
|||
// import ReactSchemaForm from './react/json-form-react';
|
|||
const reactSchemaFormObservables: Observable<any>[] = [ |
|||
from(import('react')), |
|||
from(import('react-dom')), |
|||
from(import('react-dom/client')), |
|||
from(import('./react/json-form-react')) |
|||
]; |
|||
forkJoin(reactSchemaFormObservables).subscribe( |
|||
(modules) => { |
|||
const react = unwrapModule(modules[0]); |
|||
const reactDomClient = unwrapModule(modules[2]); |
|||
const jsonFormReact = unwrapModule(modules[3]); |
|||
this.reactRoot = reactDomClient.createRoot(this.reactRootElmRef.nativeElement); |
|||
this.reactRoot.render(react.createElement(jsonFormReact, this.formProps)); |
|||
} |
|||
); |
|||
} |
|||
|
|||
private destroyReactSchemaForm() { |
|||
this.reactRoot?.unmount(); |
|||
} |
|||
|
|||
private validateModel(): boolean { |
|||
if (this.schema && this.model) { |
|||
return JsonFormUtils.validateBySchema(this.schema, this.model).valid; |
|||
} |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
@ -1,239 +0,0 @@ |
|||
/* |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
import * as React from 'react'; |
|||
import * as ReactDOM from 'react-dom'; |
|||
import ThingsboardBaseComponent from './json-form-base-component'; |
|||
import reactCSS from 'reactcss'; |
|||
import Button from '@mui/material/Button'; |
|||
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; |
|||
import { IEditorProps } from 'react-ace/src/types'; |
|||
import { map, mergeMap } from 'rxjs/operators'; |
|||
import { getAce } from '@shared/models/ace/ace.models'; |
|||
import { from, lastValueFrom } from 'rxjs'; |
|||
import { Observable } from 'rxjs/internal/Observable'; |
|||
import { CircularProgress, IconButton } from '@mui/material'; |
|||
import { MouseEvent } from 'react'; |
|||
import { Help, HelpOutline } from '@mui/icons-material'; |
|||
import { unwrapModule } from '@core/utils'; |
|||
|
|||
const ReactAce = React.lazy(() => { |
|||
return lastValueFrom(getAce().pipe( |
|||
mergeMap(() => { |
|||
return from(import('react-ace')).pipe( |
|||
map((module) => unwrapModule(module) |
|||
)); |
|||
}) |
|||
)); |
|||
}); |
|||
|
|||
interface ThingsboardAceEditorProps extends JsonFormFieldProps { |
|||
mode: string; |
|||
onTidy: (value: string) => Observable<string>; |
|||
} |
|||
|
|||
interface ThingsboardAceEditorState extends JsonFormFieldState { |
|||
isFull: boolean; |
|||
fullscreenContainerElement: Element; |
|||
helpVisible: boolean; |
|||
helpReady: boolean; |
|||
focused: boolean; |
|||
} |
|||
|
|||
class ThingsboardAceEditor extends React.Component<ThingsboardAceEditorProps, ThingsboardAceEditorState> { |
|||
|
|||
private aceEditor: IEditorProps; |
|||
|
|||
constructor(props: ThingsboardAceEditorProps) { |
|||
super(props); |
|||
this.onValueChanged = this.onValueChanged.bind(this); |
|||
this.onBlur = this.onBlur.bind(this); |
|||
this.onFocus = this.onFocus.bind(this); |
|||
this.onTidy = this.onTidy.bind(this); |
|||
this.onHelp = this.onHelp.bind(this); |
|||
this.onLoad = this.onLoad.bind(this); |
|||
this.onToggleFull = this.onToggleFull.bind(this); |
|||
const value = props.value ? props.value + '' : ''; |
|||
this.state = { |
|||
isFull: false, |
|||
fullscreenContainerElement: null, |
|||
helpVisible: false, |
|||
helpReady: true, |
|||
value, |
|||
focused: false |
|||
}; |
|||
} |
|||
|
|||
onValueChanged(value) { |
|||
this.setState({ |
|||
value |
|||
}); |
|||
this.props.onChangeValidate({ |
|||
target: { |
|||
value |
|||
} |
|||
}); |
|||
} |
|||
|
|||
onBlur() { |
|||
this.setState({ focused: false }); |
|||
} |
|||
|
|||
onFocus() { |
|||
this.setState({ focused: true }); |
|||
} |
|||
|
|||
onTidy() { |
|||
if (!this.props.form.readonly) { |
|||
const value = this.state.value; |
|||
this.props.onTidy(value).subscribe( |
|||
(processedValue) => { |
|||
this.setState({ |
|||
value: processedValue |
|||
}); |
|||
this.props.onChangeValidate({ |
|||
target: { |
|||
value: processedValue |
|||
} |
|||
}); |
|||
} |
|||
); |
|||
} |
|||
} |
|||
|
|||
onHelp(event: MouseEvent) { |
|||
if (this.state.helpVisible && !this.state.helpReady) { |
|||
event.preventDefault(); |
|||
event.stopPropagation(); |
|||
} else { |
|||
this.props.onHelpClick(event, this.props.form.helpId, |
|||
(visible) => { |
|||
this.setState({ |
|||
helpVisible: visible |
|||
}); |
|||
}, (ready) => { |
|||
this.setState({ |
|||
helpReady: ready |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
onLoad(editor: IEditorProps) { |
|||
this.aceEditor = editor; |
|||
} |
|||
|
|||
onToggleFull() { |
|||
this.props.onToggleFullscreen((el) => { |
|||
this.setState({ isFull: !this.state.isFull, fullscreenContainerElement: el }); |
|||
}); |
|||
} |
|||
|
|||
componentDidUpdate() { |
|||
} |
|||
|
|||
render() { |
|||
|
|||
const styles = reactCSS({ |
|||
default: { |
|||
tidyButtonStyle: { |
|||
color: '#7B7B7B', |
|||
minWidth: '32px', |
|||
minHeight: '15px', |
|||
lineHeight: '15px', |
|||
fontSize: '0.800rem', |
|||
margin: '0', |
|||
padding: '4px', |
|||
height: '23px', |
|||
borderRadius: '5px', |
|||
marginLeft: '5px' |
|||
} |
|||
} |
|||
}); |
|||
|
|||
let labelClass = 'tb-label'; |
|||
if (this.props.form.required) { |
|||
labelClass += ' tb-required'; |
|||
} |
|||
if (this.props.form.readonly) { |
|||
labelClass += ' tb-readonly'; |
|||
} |
|||
if (this.state.focused) { |
|||
labelClass += ' tb-focused'; |
|||
} |
|||
let containerClass = 'tb-container'; |
|||
const style = this.props.form.style || {width: '100%'}; |
|||
if (this.state.isFull) { |
|||
containerClass += ' fullscreen-form-field'; |
|||
} |
|||
const formDom = ( |
|||
<div className={containerClass}> |
|||
<label className={labelClass}>{this.props.form.title}</label> |
|||
<div className='json-form-ace-editor'> |
|||
<div className='title-panel'> |
|||
<label>{this.props.mode}</label> |
|||
{ this.props.onTidy ? <Button style={ styles.tidyButtonStyle } |
|||
className='tidy-button' onClick={this.onTidy}>Tidy</Button> : null } |
|||
{ this.props.form.helpId ? <div style={ {position: 'relative', display: 'inline-block', marginLeft: '5px'} }> |
|||
<IconButton color='primary' |
|||
className='help-button' onClick={this.onHelp}> |
|||
{this.state.helpVisible ? <Help /> : <HelpOutline /> } |
|||
</IconButton> |
|||
{ this.state.helpVisible && !this.state.helpReady ? |
|||
<div className='tb-absolute-fill help-button-loading'> |
|||
<CircularProgress size={18} thickness={4}/> |
|||
</div> : null }</div> : null } |
|||
<Button style={ styles.tidyButtonStyle } |
|||
className='tidy-button' onClick={this.onToggleFull}> |
|||
{this.state.isFull ? |
|||
'Exit fullscreen' : 'Fullscreen'} |
|||
</Button> |
|||
</div> |
|||
<React.Suspense fallback={<div>Loading...</div>}> |
|||
<ReactAce mode={this.props.mode} |
|||
theme={'textmate'} |
|||
height={this.state.isFull ? '100%' : '150px'} |
|||
width={this.state.isFull ? '100%' : '300px'} |
|||
onChange={this.onValueChanged} |
|||
onFocus={this.onFocus} |
|||
onBlur={this.onBlur} |
|||
onLoad={this.onLoad} |
|||
name={this.props.form.title} |
|||
value={this.state.value} |
|||
readOnly={this.props.form.readonly} |
|||
editorProps={{$blockScrolling: Infinity}} |
|||
enableBasicAutocompletion={true} |
|||
enableSnippets={true} |
|||
enableLiveAutocompletion={true} |
|||
style={style}/> |
|||
</React.Suspense> |
|||
</div> |
|||
<div className='json-form-error' |
|||
style={{opacity: this.props.valid ? '0' : '1'}}>{this.props.error}</div> |
|||
</div> |
|||
); |
|||
if (this.state.isFull) { |
|||
return ReactDOM.createPortal(formDom, this.state.fullscreenContainerElement); |
|||
} else { |
|||
return ( |
|||
<div> |
|||
{formDom} |
|||
</div> |
|||
); |
|||
} |
|||
} |
|||
} |
|||
|
|||
export default ThingsboardBaseComponent(ThingsboardAceEditor); |
|||
@ -1,177 +0,0 @@ |
|||
/* |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
import * as React from 'react'; |
|||
import JsonFormUtils from './json-form-utils'; |
|||
import ThingsboardBaseComponent from './json-form-base-component'; |
|||
import Button from '@mui/material/Button'; |
|||
import _ from 'lodash'; |
|||
import IconButton from '@mui/material/IconButton'; |
|||
import Clear from '@mui/icons-material/Clear'; |
|||
import Add from '@mui/icons-material/Add'; |
|||
import Tooltip from '@mui/material/Tooltip'; |
|||
import { |
|||
JsonFormData, |
|||
JsonFormFieldProps, |
|||
JsonFormFieldState |
|||
} from '@shared/components/json-form/react/json-form.models'; |
|||
|
|||
interface ThingsboardArrayState extends JsonFormFieldState { |
|||
model: any[]; |
|||
keys: number[]; |
|||
} |
|||
|
|||
class ThingsboardArray extends React.Component<JsonFormFieldProps, ThingsboardArrayState> { |
|||
|
|||
constructor(props: JsonFormFieldProps) { |
|||
super(props); |
|||
this.onAppend = this.onAppend.bind(this); |
|||
this.onDelete = this.onDelete.bind(this); |
|||
const model = JsonFormUtils.selectOrSet(this.props.form.key, this.props.model) || []; |
|||
const keys: number[] = []; |
|||
for (let i = 0; i < model.length; i++) { |
|||
keys.push(i); |
|||
} |
|||
this.state = { |
|||
model, |
|||
keys |
|||
}; |
|||
} |
|||
|
|||
componentDidMount() { |
|||
if (this.props.form.startEmpty !== true && this.state.model.length === 0) { |
|||
this.onAppend(); |
|||
} |
|||
} |
|||
|
|||
onAppend() { |
|||
let empty; |
|||
if (this.props.form && this.props.form.schema && this.props.form.schema.items) { |
|||
const items = this.props.form.schema.items; |
|||
if (items.type && items.type.indexOf('object') !== -1) { |
|||
empty = {}; |
|||
if (!this.props.options || this.props.options.setSchemaDefaults !== false) { |
|||
empty = typeof items.default !== 'undefined' ? items.default : empty; |
|||
if (empty) { |
|||
JsonFormUtils.traverseSchema(items, (prop, path) => { |
|||
if (typeof prop.default !== 'undefined') { |
|||
JsonFormUtils.selectOrSet(path, empty, prop.default); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
} else if (items.type && items.type.indexOf('array') !== -1) { |
|||
empty = []; |
|||
if (!this.props.options || this.props.options.setSchemaDefaults !== false) { |
|||
empty = items.default || empty; |
|||
} |
|||
} else { |
|||
if (!this.props.options || this.props.options.setSchemaDefaults !== false) { |
|||
empty = items.default || empty; |
|||
} |
|||
} |
|||
} |
|||
const newModel = this.state.model; |
|||
newModel.push(empty); |
|||
const newKeys = this.state.keys; |
|||
let key = 0; |
|||
if (newKeys.length > 0) { |
|||
key = newKeys[newKeys.length - 1] + 1; |
|||
} |
|||
newKeys.push(key); |
|||
this.setState({ |
|||
model: newModel, |
|||
keys: newKeys |
|||
} |
|||
); |
|||
this.props.onChangeValidate(this.state.model, true); |
|||
} |
|||
|
|||
onDelete(index: number) { |
|||
const newModel = this.state.model; |
|||
newModel.splice(index, 1); |
|||
const newKeys = this.state.keys; |
|||
newKeys.splice(index, 1); |
|||
this.setState( |
|||
{ |
|||
model: newModel, |
|||
keys: newKeys |
|||
} |
|||
); |
|||
this.props.onChangeValidate(this.state.model, true); |
|||
} |
|||
|
|||
setIndex(index: number) { |
|||
return (form: JsonFormData) => { |
|||
if (form.key) { |
|||
form.key[form.key.indexOf('')] = index; |
|||
} |
|||
}; |
|||
} |
|||
|
|||
copyWithIndex(form: JsonFormData, index: number): JsonFormData { |
|||
const copy: JsonFormData = _.cloneDeep(form); |
|||
copy.arrayIndex = index; |
|||
JsonFormUtils.traverseForm(copy, this.setIndex(index)); |
|||
return copy; |
|||
} |
|||
|
|||
render() { |
|||
const arrays = []; |
|||
const model = this.state.model; |
|||
const keys = this.state.keys; |
|||
for (let i = 0; i < model.length; i++ ) { |
|||
let removeButton: React.JSX.Element = null; |
|||
if (!this.props.form.readonly) { |
|||
const boundOnDelete = this.onDelete.bind(this, i); |
|||
removeButton = <Tooltip title='Remove'><IconButton onClick={boundOnDelete}><Clear/></IconButton></Tooltip>; |
|||
} |
|||
const forms = (this.props.form.items as JsonFormData[]).map((form, index) => { |
|||
const copy = this.copyWithIndex(form, i); |
|||
return this.props.builder(copy, this.props.model, index, this.props.onChange, |
|||
this.props.onColorClick, this.props.onIconClick, this.props.onToggleFullscreen, |
|||
this.props.onHelpClick, this.props.mapper); |
|||
}); |
|||
arrays.push( |
|||
<li key={keys[i]} className='list-group-item'> |
|||
{removeButton} |
|||
{forms} |
|||
</li> |
|||
); |
|||
} |
|||
let addButton: JSX.Element = null; |
|||
if (!this.props.form.readonly) { |
|||
addButton = <Button variant='contained' |
|||
color='primary' |
|||
startIcon={<Add/>} |
|||
style={{marginBottom: '8px'}} |
|||
onClick={this.onAppend}>{this.props.form.add || 'New'}</Button>; |
|||
} |
|||
|
|||
return ( |
|||
<div> |
|||
<div className='tb-container'> |
|||
<div className='tb-head-label'>{this.props.form.title}</div> |
|||
<ol className='list-group'> |
|||
{arrays} |
|||
</ol> |
|||
</div> |
|||
{addButton} |
|||
</div> |
|||
); |
|||
} |
|||
} |
|||
|
|||
export default ThingsboardBaseComponent(ThingsboardArray); |
|||
@ -1,122 +0,0 @@ |
|||
/* |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
import * as React from 'react'; |
|||
import JsonFormUtils from './json-form-utils'; |
|||
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; |
|||
import { isDefinedAndNotNull } from '@core/utils'; |
|||
|
|||
export default ThingsboardBaseComponent => class<P extends JsonFormFieldProps> |
|||
extends React.Component<P, JsonFormFieldState> { |
|||
|
|||
constructor(props: P) { |
|||
super(props); |
|||
this.onChangeValidate = this.onChangeValidate.bind(this); |
|||
const value = this.defaultValue(); |
|||
const validationResult = JsonFormUtils.validate(this.props.form, value); |
|||
this.state = { |
|||
value, |
|||
valid: !!(validationResult.valid || !value), |
|||
error: !validationResult.valid && value ? validationResult.error.message : null |
|||
}; |
|||
} |
|||
|
|||
componentDidMount() { |
|||
if (typeof this.state.value !== 'undefined') { |
|||
this.props.onChange(this.props.form.key, this.state.value); |
|||
} |
|||
} |
|||
|
|||
onChangeValidate(e, forceUpdate?: boolean) { |
|||
let value = null; |
|||
if (this.props.form.schema.type === 'integer' || this.props.form.schema.type === 'number') { |
|||
if (!e || e.target?.value === null || e.target?.value === '') { |
|||
value = undefined; |
|||
} else if (typeof e === 'number') { |
|||
value = Number(e); |
|||
} else if (e.target.value.indexOf('.') === -1) { |
|||
value = parseInt(e.target.value, 10); |
|||
} else { |
|||
value = parseFloat(e.target.value); |
|||
} |
|||
} else if (this.props.form.schema.type === 'boolean') { |
|||
value = e.target.checked; |
|||
} else if (this.props.form.schema.type === 'date' || this.props.form.schema.type === 'array') { |
|||
value = e; |
|||
} else { // string
|
|||
value = e.target.value; |
|||
} |
|||
const validationResult = JsonFormUtils.validate(this.props.form, value); |
|||
this.setState({ |
|||
value, |
|||
valid: validationResult.valid, |
|||
error: validationResult.valid ? null : validationResult.error.message |
|||
}); |
|||
this.props.onChange(this.props.form.key, value, forceUpdate); |
|||
} |
|||
|
|||
defaultValue() { |
|||
let value = JsonFormUtils.selectOrSet(this.props.form.key, this.props.model); |
|||
if (this.props.form.schema.type === 'boolean') { |
|||
if (typeof value !== 'boolean' && typeof this.props.form.default === 'boolean') { |
|||
value = this.props.form.default; |
|||
} |
|||
if (typeof value !== 'boolean' && this.props.form.schema && typeof this.props.form.schema.default === 'boolean') { |
|||
value = this.props.form.schema.default; |
|||
} |
|||
if (typeof value !== 'boolean' && |
|||
this.props.form.schema && |
|||
this.props.form.required) { |
|||
value = false; |
|||
} |
|||
} else if (this.props.form.schema.type === 'integer' || this.props.form.schema.type === 'number') { |
|||
if (typeof value !== 'number' && typeof this.props.form.default === 'number') { |
|||
value = this.props.form.default; |
|||
} |
|||
if (typeof value !== 'number' && this.props.form.schema && typeof this.props.form.schema.default === 'number') { |
|||
value = this.props.form.schema.default; |
|||
} |
|||
if (typeof value !== 'number' && this.props.form.titleMap && typeof this.props.form.titleMap[0].value === 'number') { |
|||
value = this.props.form.titleMap[0].value; |
|||
} |
|||
if (value && typeof value === 'string') { |
|||
if (value.indexOf('.') === -1) { |
|||
value = parseInt(value, 10); |
|||
} else { |
|||
value = parseFloat(value); |
|||
} |
|||
} |
|||
} else { |
|||
if (!value && isDefinedAndNotNull(this.props.form.default)) { |
|||
value = this.props.form.default; |
|||
} |
|||
if (!value && this.props.form.schema && isDefinedAndNotNull(this.props.form.schema.default)) { |
|||
value = this.props.form.schema.default; |
|||
} |
|||
if (!value && this.props.form.titleMap && isDefinedAndNotNull(this.props.form.titleMap[0].value)) { |
|||
value = this.props.form.titleMap[0].value; |
|||
} |
|||
} |
|||
return value; |
|||
} |
|||
|
|||
render() { |
|||
if (this.props.form && this.props.form.schema) { |
|||
return <ThingsboardBaseComponent {...this.props} {...this.state} onChangeValidate={this.onChangeValidate}/>; |
|||
} else { |
|||
return <div></div>; |
|||
} |
|||
} |
|||
}; |
|||
@ -1,46 +0,0 @@ |
|||
/* |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
import * as React from 'react'; |
|||
import ThingsboardBaseComponent from './json-form-base-component'; |
|||
import Checkbox from '@mui/material/Checkbox'; |
|||
import { JsonFormFieldProps, JsonFormFieldState } from './json-form.models.js'; |
|||
import FormControlLabel from '@mui/material/FormControlLabel'; |
|||
|
|||
class ThingsboardCheckbox extends React.Component<JsonFormFieldProps, JsonFormFieldState> { |
|||
render() { |
|||
return ( |
|||
<div> |
|||
<FormControlLabel |
|||
control={ |
|||
<Checkbox |
|||
color={'secondary'} |
|||
name={this.props.form.key.slice(-1)[0] + ''} |
|||
value={this.props.form.key.slice(-1)[0]} |
|||
checked={this.props.value || false} |
|||
disabled={this.props.form.readonly} |
|||
onChange={(e) => { |
|||
this.props.onChangeValidate(e); |
|||
}} |
|||
/> |
|||
} |
|||
label={this.props.form.title} |
|||
/> |
|||
</div> |
|||
); |
|||
} |
|||
} |
|||
|
|||
export default ThingsboardBaseComponent(ThingsboardCheckbox); |
|||
@ -1,186 +0,0 @@ |
|||
/* |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
import * as React from 'react'; |
|||
import ThingsboardBaseComponent from './json-form-base-component'; |
|||
import reactCSS from 'reactcss'; |
|||
import tinycolor from 'tinycolor2'; |
|||
import TextField from '@mui/material/TextField'; |
|||
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; |
|||
import IconButton from '@mui/material/IconButton'; |
|||
import Clear from '@mui/icons-material/Clear'; |
|||
import Tooltip from '@mui/material/Tooltip'; |
|||
|
|||
interface ThingsboardColorState extends JsonFormFieldState { |
|||
color: tinycolor.ColorFormats.RGBA | null; |
|||
focused: boolean; |
|||
} |
|||
|
|||
class ThingsboardColor extends React.Component<JsonFormFieldProps, ThingsboardColorState> { |
|||
|
|||
containerRef = React.createRef<HTMLDivElement>(); |
|||
|
|||
constructor(props: JsonFormFieldProps) { |
|||
super(props); |
|||
this.onBlur = this.onBlur.bind(this); |
|||
this.onFocus = this.onFocus.bind(this); |
|||
this.onValueChanged = this.onValueChanged.bind(this); |
|||
this.onSwatchClick = this.onSwatchClick.bind(this); |
|||
this.onClear = this.onClear.bind(this); |
|||
const value = props.value ? props.value + '' : null; |
|||
const color = value != null ? tinycolor(value).toRgb() : null; |
|||
this.state = { |
|||
color, |
|||
focused: false |
|||
}; |
|||
} |
|||
|
|||
onBlur() { |
|||
this.setState({focused: false}); |
|||
} |
|||
|
|||
onFocus() { |
|||
this.setState({focused: true}); |
|||
} |
|||
|
|||
componentDidMount() { |
|||
const node = this.containerRef.current; |
|||
const colContainer = $(node).children('#color-container'); |
|||
colContainer.click(() => { |
|||
if (!this.props.form.readonly) { |
|||
this.onSwatchClick(); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
componentWillUnmount() { |
|||
const node = this.containerRef.current; |
|||
const colContainer = $(node).children('#color-container'); |
|||
colContainer.off( 'click' ); |
|||
} |
|||
|
|||
onValueChanged(value: tinycolor.ColorFormats.RGBA | null) { |
|||
let color: tinycolor.Instance = null; |
|||
if (value != null) { |
|||
color = tinycolor(value); |
|||
} |
|||
this.setState({ |
|||
color: value |
|||
}); |
|||
let colorValue = ''; |
|||
if (color != null && color.getAlpha() !== 1) { |
|||
colorValue = color.toRgbString(); |
|||
} else if (color != null) { |
|||
colorValue = color.toHexString(); |
|||
} |
|||
this.props.onChangeValidate({ |
|||
target: { |
|||
value: colorValue |
|||
} |
|||
}); |
|||
} |
|||
|
|||
onSwatchClick() { |
|||
this.props.onColorClick(this.props.form.key, this.state.color, |
|||
(color) => { |
|||
this.onValueChanged(color); |
|||
} |
|||
); |
|||
} |
|||
|
|||
onClear(event: React.MouseEvent) { |
|||
if (event) { |
|||
event.stopPropagation(); |
|||
} |
|||
this.onValueChanged(null); |
|||
} |
|||
|
|||
render() { |
|||
|
|||
let background = 'rgba(0,0,0,0)'; |
|||
if (this.state.color != null) { |
|||
background = `rgba(${ this.state.color.r }, ${ this.state.color.g }, ${ this.state.color.b }, ${ this.state.color.a })`; |
|||
} |
|||
|
|||
const styles = reactCSS({ |
|||
default: { |
|||
color: { |
|||
background: `${ background }` |
|||
}, |
|||
swatch: { |
|||
display: 'inline-block', |
|||
marginRight: '10px', |
|||
marginTop: 'auto', |
|||
marginBottom: 'auto', |
|||
cursor: 'pointer', |
|||
opacity: `${ this.props.form.readonly ? '0.6' : '1' }` |
|||
}, |
|||
swatchText: { |
|||
width: '100%' |
|||
}, |
|||
container: { |
|||
display: 'flex', |
|||
flexDirection: 'row', |
|||
alignItems: 'center' |
|||
}, |
|||
colorContainer: { |
|||
display: 'flex', |
|||
width: '100%' |
|||
} |
|||
}, |
|||
}); |
|||
|
|||
let fieldClass = 'tb-field'; |
|||
if (this.props.form.required) { |
|||
fieldClass += ' tb-required'; |
|||
} |
|||
if (this.props.form.readonly) { |
|||
fieldClass += ' tb-readonly'; |
|||
} |
|||
if (this.state.focused) { |
|||
fieldClass += ' tb-focused'; |
|||
} |
|||
|
|||
let stringColor = ''; |
|||
if (this.state.color != null) { |
|||
const color = tinycolor(this.state.color); |
|||
stringColor = color.toRgbString(); |
|||
} |
|||
|
|||
return ( |
|||
<div ref={this.containerRef} style={ styles.container }> |
|||
<div id='color-container' style={ styles.colorContainer }> |
|||
<div className='tb-color-preview' style={ styles.swatch }> |
|||
<div className='tb-color-result' style={ styles.color }/> |
|||
</div> |
|||
<TextField |
|||
variant={'standard'} |
|||
className={fieldClass} |
|||
label={this.props.form.title} |
|||
error={!this.props.valid} |
|||
helperText={this.props.valid ? this.props.form.placeholder : this.props.error} |
|||
value={stringColor} |
|||
disabled={this.props.form.readonly} |
|||
onFocus={this.onFocus} |
|||
onBlur={this.onBlur} |
|||
style={ styles.swatchText }/> |
|||
</div> |
|||
<Tooltip title='Clear' placement='top'><IconButton onClick={this.onClear}><Clear/></IconButton></Tooltip> |
|||
</div> |
|||
); |
|||
} |
|||
} |
|||
|
|||
export default ThingsboardBaseComponent(ThingsboardColor); |
|||
@ -1,40 +0,0 @@ |
|||
/* |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
import * as React from 'react'; |
|||
import ThingsboardAceEditor from './json-form-ace-editor'; |
|||
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; |
|||
import { Observable } from 'rxjs/internal/Observable'; |
|||
import { beautifyCss } from '@shared/models/beautify.models'; |
|||
|
|||
class ThingsboardCss extends React.Component<JsonFormFieldProps, JsonFormFieldState> { |
|||
|
|||
constructor(props: JsonFormFieldProps) { |
|||
super(props); |
|||
this.onTidyCss = this.onTidyCss.bind(this); |
|||
} |
|||
|
|||
onTidyCss(css: string): Observable<string> { |
|||
return beautifyCss(css, {indent_size: 4}); |
|||
} |
|||
|
|||
render() { |
|||
return ( |
|||
<ThingsboardAceEditor {...this.props} mode='css' onTidy={this.onTidyCss} {...this.state}></ThingsboardAceEditor> |
|||
); |
|||
} |
|||
} |
|||
|
|||
export default ThingsboardCss; |
|||
@ -1,83 +0,0 @@ |
|||
/* |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
import * as React from 'react'; |
|||
import ThingsboardBaseComponent from './json-form-base-component'; |
|||
import { AdapterMoment } from '@mui/x-date-pickers/AdapterMoment' |
|||
import { LocalizationProvider, DatePicker } from '@mui/x-date-pickers'; |
|||
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; |
|||
import moment from 'moment'; |
|||
|
|||
interface ThingsboardDateState extends JsonFormFieldState { |
|||
currentValue: Date | null; |
|||
} |
|||
|
|||
class ThingsboardDate extends React.Component<JsonFormFieldProps, ThingsboardDateState> { |
|||
|
|||
constructor(props: JsonFormFieldProps) { |
|||
super(props); |
|||
this.onDatePicked = this.onDatePicked.bind(this); |
|||
let value: Date | null = null; |
|||
if (this.props.value && typeof this.props.value === 'number') { |
|||
value = new Date(this.props.value); |
|||
} |
|||
this.state = { |
|||
currentValue: value |
|||
}; |
|||
} |
|||
|
|||
|
|||
onDatePicked(date: moment.Moment | null) { |
|||
this.setState({ |
|||
currentValue: date?.toDate() |
|||
}); |
|||
this.props.onChangeValidate(date ? date.valueOf() : null); |
|||
} |
|||
|
|||
render() { |
|||
|
|||
let fieldClass = 'tb-date-field'; |
|||
if (this.props.form.required) { |
|||
fieldClass += ' tb-required'; |
|||
} |
|||
if (this.props.form.readonly) { |
|||
fieldClass += ' tb-readonly'; |
|||
} |
|||
|
|||
return ( |
|||
<LocalizationProvider dateAdapter={AdapterMoment}> |
|||
<div style={{width: '100%', display: 'block'}}> |
|||
<DatePicker |
|||
slotProps={{ |
|||
textField: { |
|||
variant: 'standard' |
|||
} |
|||
}} |
|||
format='MM/DD/YYYY' |
|||
className={fieldClass} |
|||
label={this.props.form.title} |
|||
value={moment(this.state.currentValue?.valueOf())} |
|||
onChange={this.onDatePicked} |
|||
disabled={this.props.form.readonly} |
|||
sx={this.props.form.style || {width: '100%'}} |
|||
/> |
|||
|
|||
</div> |
|||
</LocalizationProvider> |
|||
); |
|||
} |
|||
} |
|||
|
|||
export default ThingsboardBaseComponent(ThingsboardDate); |
|||
@ -1,44 +0,0 @@ |
|||
/* |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
import * as React from 'react'; |
|||
import { |
|||
JsonFormData, |
|||
JsonFormFieldProps, |
|||
JsonFormFieldState |
|||
} from '@shared/components/json-form/react/json-form.models'; |
|||
|
|||
class ThingsboardFieldSet extends React.Component<JsonFormFieldProps, JsonFormFieldState> { |
|||
|
|||
render() { |
|||
const forms = (this.props.form.items as JsonFormData[]).map((form: JsonFormData, index) => { |
|||
return this.props.builder(form, this.props.model, index, this.props.onChange, |
|||
this.props.onColorClick, this.props.onIconClick, this.props.onToggleFullscreen, this.props.onHelpClick, this.props.mapper); |
|||
}); |
|||
|
|||
return ( |
|||
<div style={{paddingTop: '20px'}}> |
|||
<div className='tb-head-label'> |
|||
{this.props.form.title} |
|||
</div> |
|||
<div> |
|||
{forms} |
|||
</div> |
|||
</div> |
|||
); |
|||
} |
|||
} |
|||
|
|||
export default ThingsboardFieldSet; |
|||
@ -1,27 +0,0 @@ |
|||
/* |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
import * as React from 'react'; |
|||
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; |
|||
|
|||
class ThingsboardHelp extends React.Component<JsonFormFieldProps, JsonFormFieldState> { |
|||
render() { |
|||
return ( |
|||
<div className={this.props.form.htmlClass} dangerouslySetInnerHTML={{__html: this.props.form.description}} ></div> |
|||
); |
|||
} |
|||
} |
|||
|
|||
export default ThingsboardHelp; |
|||
@ -1,40 +0,0 @@ |
|||
/* |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
import * as React from 'react'; |
|||
import ThingsboardAceEditor from './json-form-ace-editor'; |
|||
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; |
|||
import { Observable } from 'rxjs/internal/Observable'; |
|||
import { beautifyHtml } from '@shared/models/beautify.models'; |
|||
|
|||
class ThingsboardHtml extends React.Component<JsonFormFieldProps, JsonFormFieldState> { |
|||
|
|||
constructor(props: JsonFormFieldProps) { |
|||
super(props); |
|||
this.onTidyHtml = this.onTidyHtml.bind(this); |
|||
} |
|||
|
|||
onTidyHtml(html: string): Observable<string> { |
|||
return beautifyHtml(html, {indent_size: 4}); |
|||
} |
|||
|
|||
render() { |
|||
return ( |
|||
<ThingsboardAceEditor {...this.props} mode='html' onTidy={this.onTidyHtml} {...this.state}></ThingsboardAceEditor> |
|||
); |
|||
} |
|||
} |
|||
|
|||
export default ThingsboardHtml; |
|||
@ -1,162 +0,0 @@ |
|||
/* |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
import * as React from 'react'; |
|||
import { MouseEvent } from 'react'; |
|||
import ThingsboardBaseComponent from './json-form-base-component'; |
|||
import reactCSS from 'reactcss'; |
|||
import TextField from '@mui/material/TextField'; |
|||
import IconButton from '@mui/material/IconButton'; |
|||
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; |
|||
import Clear from '@mui/icons-material/Clear'; |
|||
import Icon from '@mui/material/Icon'; |
|||
import Tooltip from '@mui/material/Tooltip'; |
|||
|
|||
interface ThingsboardIconState extends JsonFormFieldState { |
|||
icon: string | null; |
|||
focused: boolean; |
|||
} |
|||
|
|||
class ThingsboardIcon extends React.Component<JsonFormFieldProps, ThingsboardIconState> { |
|||
|
|||
containerRef = React.createRef<HTMLDivElement>(); |
|||
|
|||
constructor(props: JsonFormFieldProps) { |
|||
super(props); |
|||
this.onBlur = this.onBlur.bind(this); |
|||
this.onFocus = this.onFocus.bind(this); |
|||
this.onValueChanged = this.onValueChanged.bind(this); |
|||
this.onIconClick = this.onIconClick.bind(this); |
|||
this.onClear = this.onClear.bind(this); |
|||
const icon = props.value ? props.value : ''; |
|||
this.state = { |
|||
icon, |
|||
focused: false |
|||
}; |
|||
} |
|||
|
|||
onBlur() { |
|||
this.setState({focused: false}); |
|||
} |
|||
|
|||
onFocus() { |
|||
this.setState({focused: true}); |
|||
} |
|||
|
|||
componentDidMount() { |
|||
const node = this.containerRef.current; |
|||
const iconContainer = $(node).children('#icon-container'); |
|||
iconContainer.on('click', (event) => { |
|||
if (!this.props.form.readonly) { |
|||
this.onIconClick(event); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
componentWillUnmount() { |
|||
const node = this.containerRef.current; |
|||
const iconContainer = $(node).children('#icon-container'); |
|||
iconContainer.off( 'click' ); |
|||
} |
|||
|
|||
onValueChanged(value: string | null) { |
|||
this.setState({ |
|||
icon: value |
|||
}); |
|||
this.props.onChange(this.props.form.key, value); |
|||
} |
|||
|
|||
onIconClick(_event) { |
|||
this.props.onIconClick(this.props.form.key, this.state.icon, |
|||
(color) => { |
|||
this.onValueChanged(color); |
|||
} |
|||
); |
|||
} |
|||
|
|||
onClear(event: MouseEvent) { |
|||
if (event) { |
|||
event.stopPropagation(); |
|||
} |
|||
this.onValueChanged(''); |
|||
} |
|||
|
|||
render() { |
|||
|
|||
const styles = reactCSS({ |
|||
default: { |
|||
container: { |
|||
display: 'flex', |
|||
flexDirection: 'row', |
|||
alignItems: 'center' |
|||
}, |
|||
icon: { |
|||
padding: '12px', |
|||
marginRight: '10px', |
|||
marginBottom: 'auto', |
|||
cursor: 'pointer', |
|||
border: 'solid 1px rgba(0, 0, 0, .27)', |
|||
borderRadius: '0' |
|||
}, |
|||
iconContainer: { |
|||
display: 'flex', |
|||
width: '100%' |
|||
}, |
|||
iconText: { |
|||
width: '100%' |
|||
}, |
|||
}, |
|||
}); |
|||
|
|||
let fieldClass = 'tb-field'; |
|||
if (this.props.form.required) { |
|||
fieldClass += ' tb-required'; |
|||
} |
|||
if (this.state.focused) { |
|||
fieldClass += ' tb-focused'; |
|||
} |
|||
|
|||
let pickedIcon = 'more_horiz'; |
|||
let icon = ''; |
|||
if (this.state.icon !== '') { |
|||
pickedIcon = this.state.icon; |
|||
icon = this.state.icon; |
|||
} |
|||
|
|||
return ( |
|||
<div ref={this.containerRef} style={ styles.container }> |
|||
<div id='icon-container' style={ styles.iconContainer }> |
|||
<IconButton style={ styles.icon }> |
|||
<Icon>{pickedIcon}</Icon> |
|||
</IconButton> |
|||
<TextField |
|||
variant={'standard'} |
|||
className={fieldClass} |
|||
label={this.props.form.title} |
|||
error={!this.props.valid} |
|||
helperText={this.props.valid ? this.props.form.placeholder : this.props.error} |
|||
value={icon} |
|||
disabled={this.props.form.readonly} |
|||
onFocus={this.onFocus} |
|||
onBlur={this.onBlur} |
|||
style={ styles.iconText } /> |
|||
</div> |
|||
<Tooltip title='Clear' placement='top'><IconButton onClick={this.onClear}><Clear/></IconButton></Tooltip> |
|||
</div> |
|||
); |
|||
} |
|||
} |
|||
|
|||
export default ThingsboardBaseComponent(ThingsboardIcon); |
|||
@ -1,108 +0,0 @@ |
|||
/* |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
import React, { MouseEvent } from 'react'; |
|||
import Dropzone from 'react-dropzone'; |
|||
import ThingsboardBaseComponent from './json-form-base-component'; |
|||
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; |
|||
import IconButton from '@mui/material/IconButton'; |
|||
import Clear from '@mui/icons-material/Clear'; |
|||
import Tooltip from '@mui/material/Tooltip'; |
|||
|
|||
interface ThingsboardImageState extends JsonFormFieldState { |
|||
imageUrl: string; |
|||
} |
|||
|
|||
class ThingsboardImage extends React.Component<JsonFormFieldProps, ThingsboardImageState> { |
|||
|
|||
constructor(props: JsonFormFieldProps) { |
|||
super(props); |
|||
this.onDrop = this.onDrop.bind(this); |
|||
this.onClear = this.onClear.bind(this); |
|||
const value = props.value ? props.value + '' : null; |
|||
this.state = { |
|||
imageUrl: value |
|||
}; |
|||
} |
|||
|
|||
onDrop(acceptedFiles: File[]) { |
|||
const reader = new FileReader(); |
|||
reader.onload = () => { |
|||
this.onValueChanged(reader.result as string); |
|||
}; |
|||
reader.readAsDataURL(acceptedFiles[0]); |
|||
} |
|||
|
|||
onValueChanged(value: string) { |
|||
this.setState({ |
|||
imageUrl: value |
|||
}); |
|||
this.props.onChangeValidate({ |
|||
target: { |
|||
value |
|||
} |
|||
}); |
|||
} |
|||
|
|||
onClear(event: MouseEvent) { |
|||
if (event) { |
|||
event.stopPropagation(); |
|||
} |
|||
this.onValueChanged(''); |
|||
} |
|||
|
|||
render() { |
|||
|
|||
let labelClass = 'tb-label'; |
|||
if (this.props.form.required) { |
|||
labelClass += ' tb-required'; |
|||
} |
|||
if (this.props.form.readonly) { |
|||
labelClass += ' tb-readonly'; |
|||
} |
|||
|
|||
let previewComponent: React.JSX.Element; |
|||
if (this.state.imageUrl) { |
|||
previewComponent = <img className='tb-image-preview' src={this.state.imageUrl} />; |
|||
} else { |
|||
previewComponent = <div>No image selected</div>; |
|||
} |
|||
|
|||
return ( |
|||
<div className='tb-container'> |
|||
<label className={labelClass}>{this.props.form.title}</label> |
|||
<div className='tb-image-select-container'> |
|||
<div className='tb-image-preview-container'>{previewComponent}</div> |
|||
<div className='tb-image-clear-container'> |
|||
<Tooltip title='Clear' placement='top'> |
|||
<IconButton className='tb-image-clear-btn' onClick={this.onClear}><Clear/></IconButton> |
|||
</Tooltip> |
|||
</div> |
|||
<Dropzone onDrop={this.onDrop} |
|||
accept={ {'image/*': []} } multiple={false}> |
|||
{({getRootProps, getInputProps}) => ( |
|||
<div className='tb-dropzone' {...getRootProps()}> |
|||
<div>Drop an image or click to select a file to upload.</div> |
|||
<input {...getInputProps()} /> |
|||
</div> |
|||
)} |
|||
</Dropzone> |
|||
</div> |
|||
</div> |
|||
); |
|||
} |
|||
} |
|||
|
|||
export default ThingsboardBaseComponent(ThingsboardImage); |
|||
@ -1,40 +0,0 @@ |
|||
/* |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
import * as React from 'react'; |
|||
import ThingsboardAceEditor from './json-form-ace-editor'; |
|||
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; |
|||
import { Observable } from 'rxjs/internal/Observable'; |
|||
import { beautifyJs } from '@shared/models/beautify.models'; |
|||
|
|||
class ThingsboardJavaScript extends React.Component<JsonFormFieldProps, JsonFormFieldState> { |
|||
|
|||
constructor(props: JsonFormFieldProps) { |
|||
super(props); |
|||
this.onTidyJavascript = this.onTidyJavascript.bind(this); |
|||
} |
|||
|
|||
onTidyJavascript(javascript: string): Observable<string> { |
|||
return beautifyJs(javascript, {indent_size: 4, wrap_line_length: 60}); |
|||
} |
|||
|
|||
render() { |
|||
return ( |
|||
<ThingsboardAceEditor {...this.props} mode='javascript' onTidy={this.onTidyJavascript} {...this.state}></ThingsboardAceEditor> |
|||
); |
|||
} |
|||
} |
|||
|
|||
export default ThingsboardJavaScript; |
|||
@ -1,40 +0,0 @@ |
|||
/* |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
import * as React from 'react'; |
|||
import ThingsboardAceEditor from './json-form-ace-editor'; |
|||
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; |
|||
import { Observable } from 'rxjs/internal/Observable'; |
|||
import { beautifyJs } from '@shared/models/beautify.models'; |
|||
|
|||
class ThingsboardJson extends React.Component<JsonFormFieldProps, JsonFormFieldState> { |
|||
|
|||
constructor(props: JsonFormFieldProps) { |
|||
super(props); |
|||
this.onTidyJson = this.onTidyJson.bind(this); |
|||
} |
|||
|
|||
onTidyJson(json: string): Observable<string> { |
|||
return beautifyJs(json, {indent_size: 4}); |
|||
} |
|||
|
|||
render() { |
|||
return ( |
|||
<ThingsboardAceEditor {...this.props} mode='json' onTidy={this.onTidyJson} {...this.state}></ThingsboardAceEditor> |
|||
); |
|||
} |
|||
} |
|||
|
|||
export default ThingsboardJson; |
|||
@ -1,33 +0,0 @@ |
|||
/* |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
import * as React from 'react'; |
|||
import ThingsboardAceEditor from './json-form-ace-editor'; |
|||
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; |
|||
|
|||
class ThingsboardMarkdown extends React.Component<JsonFormFieldProps, JsonFormFieldState> { |
|||
|
|||
constructor(props: JsonFormFieldProps) { |
|||
super(props); |
|||
} |
|||
|
|||
render() { |
|||
return ( |
|||
<ThingsboardAceEditor {...this.props} mode='markdown' {...this.state}></ThingsboardAceEditor> |
|||
); |
|||
} |
|||
} |
|||
|
|||
export default ThingsboardMarkdown; |
|||
@ -1,99 +0,0 @@ |
|||
/* |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
import * as React from 'react'; |
|||
import ThingsboardBaseComponent from './json-form-base-component'; |
|||
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; |
|||
import { TextField } from '@mui/material'; |
|||
import { ChangeEvent } from 'react'; |
|||
|
|||
interface ThingsboardNumberState extends JsonFormFieldState { |
|||
focused: boolean; |
|||
lastSuccessfulValue: number; |
|||
} |
|||
|
|||
class ThingsboardNumber extends React.Component<JsonFormFieldProps, ThingsboardNumberState> { |
|||
|
|||
constructor(props: JsonFormFieldProps) { |
|||
super(props); |
|||
this.preValidationCheck = this.preValidationCheck.bind(this); |
|||
this.onBlur = this.onBlur.bind(this); |
|||
this.onFocus = this.onFocus.bind(this); |
|||
this.state = { |
|||
lastSuccessfulValue: this.props.value, |
|||
focused: false |
|||
}; |
|||
} |
|||
|
|||
isNumeric(n: any) { |
|||
return n === null || n === '' || !isNaN(n) && isFinite(n); |
|||
} |
|||
|
|||
onBlur() { |
|||
this.setState({focused: false}); |
|||
} |
|||
|
|||
onFocus() { |
|||
this.setState({focused: true}); |
|||
} |
|||
|
|||
preValidationCheck(e: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) { |
|||
if (this.isNumeric(e.target.value)) { |
|||
this.setState({ |
|||
lastSuccessfulValue: Number(e.target.value) |
|||
}); |
|||
this.props.onChangeValidate(e); |
|||
} |
|||
} |
|||
|
|||
render() { |
|||
|
|||
let fieldClass = 'tb-field'; |
|||
if (this.props.form.required) { |
|||
fieldClass += ' tb-required'; |
|||
} |
|||
if (this.props.form.readonly) { |
|||
fieldClass += ' tb-readonly'; |
|||
} |
|||
if (this.state.focused) { |
|||
fieldClass += ' tb-focused'; |
|||
} |
|||
let value = this.state.lastSuccessfulValue; |
|||
if (typeof value !== 'undefined') { |
|||
value = Number(value); |
|||
} else { |
|||
value = null; |
|||
} |
|||
return ( |
|||
<div> |
|||
<TextField |
|||
variant={'standard'} |
|||
className={fieldClass} |
|||
label={this.props.form.title} |
|||
type='number' |
|||
error={!this.props.valid} |
|||
helperText={this.props.valid ? this.props.form.placeholder : this.props.error} |
|||
onChange={this.preValidationCheck} |
|||
defaultValue={value} |
|||
disabled={this.props.form.readonly} |
|||
onFocus={this.onFocus} |
|||
onBlur={this.onBlur} |
|||
style={this.props.form.style || {width: '100%'}}/> |
|||
</div> |
|||
); |
|||
} |
|||
} |
|||
|
|||
export default ThingsboardBaseComponent(ThingsboardNumber); |
|||
@ -1,51 +0,0 @@ |
|||
/* |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
import * as React from 'react'; |
|||
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; |
|||
import FormControlLabel from '@mui/material/FormControlLabel'; |
|||
import { FormLabel, Radio, RadioGroup } from '@mui/material'; |
|||
import FormControl from '@mui/material/FormControl'; |
|||
import ThingsboardBaseComponent from '@shared/components/json-form/react/json-form-base-component'; |
|||
|
|||
class ThingsboardRadios extends React.Component<JsonFormFieldProps, JsonFormFieldState> { |
|||
render() { |
|||
const items = this.props.form.titleMap.map((item, index) => { |
|||
return ( |
|||
<FormControlLabel value={item.value} control={<Radio color={'secondary'} />} label={item.name} key={index} /> |
|||
); |
|||
}); |
|||
|
|||
let row = false; |
|||
if (this.props.form.direction === 'row') { |
|||
row = true; |
|||
} |
|||
|
|||
return ( |
|||
<FormControl component='fieldset' |
|||
className={this.props.form.htmlClass} |
|||
disabled={this.props.form.readonly}> |
|||
<FormLabel component='legend'>{this.props.form.title}</FormLabel> |
|||
<RadioGroup row={row} name={this.props.form.title} value={this.props.value} onChange={(e) => { |
|||
this.props.onChangeValidate(e); |
|||
}}> |
|||
{items} |
|||
</RadioGroup> |
|||
</FormControl> |
|||
); |
|||
} |
|||
} |
|||
|
|||
export default ThingsboardBaseComponent(ThingsboardRadios); |
|||
@ -1,202 +0,0 @@ |
|||
/* |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
import * as React from 'react'; |
|||
import ThingsboardBaseComponent from './json-form-base-component'; |
|||
import Select, { Option } from 'rc-select'; |
|||
import { |
|||
JsonFormFieldProps, |
|||
JsonFormFieldState, |
|||
KeyLabelItem |
|||
} from '@shared/components/json-form/react/json-form.models'; |
|||
import { Mode } from 'rc-select/lib/interface'; |
|||
import { deepClone } from '@core/utils'; |
|||
|
|||
interface ThingsboardRcSelectState extends JsonFormFieldState { |
|||
currentValue: KeyLabelItem | KeyLabelItem[]; |
|||
items: Array<KeyLabelItem>; |
|||
focused: boolean; |
|||
} |
|||
|
|||
class ThingsboardRcSelect extends React.Component<JsonFormFieldProps, ThingsboardRcSelectState> { |
|||
|
|||
constructor(props: JsonFormFieldProps) { |
|||
super(props); |
|||
this.onSelect = this.onSelect.bind(this); |
|||
this.onDeselect = this.onDeselect.bind(this); |
|||
this.onBlur = this.onBlur.bind(this); |
|||
this.onFocus = this.onFocus.bind(this); |
|||
this.state = { |
|||
currentValue: this.keyToCurrentValue(this.props.value, this.props.form.schema.type === 'array'), |
|||
items: this.props.form.items as KeyLabelItem[], |
|||
focused: false |
|||
}; |
|||
} |
|||
|
|||
keyToCurrentValue(key: string | string[], isArray: boolean): KeyLabelItem | KeyLabelItem[] { |
|||
let currentValue: KeyLabelItem | KeyLabelItem[] = isArray ? [] : null; |
|||
if (isArray) { |
|||
const keys = key; |
|||
if (keys) { |
|||
(keys as string[]).forEach((keyVal) => { |
|||
(currentValue as KeyLabelItem[]).push({key: keyVal, label: this.labelFromKey(keyVal)}); |
|||
}); |
|||
} |
|||
} else { |
|||
currentValue = {key: key as string, label: this.labelFromKey(key as string)}; |
|||
} |
|||
return currentValue; |
|||
} |
|||
|
|||
labelFromKey(key: string): string { |
|||
let label = key || ''; |
|||
if (key) { |
|||
for (const item of this.props.form.items) { |
|||
if (item.value === key) { |
|||
label = item.label; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
return label; |
|||
} |
|||
|
|||
arrayValues(items: KeyLabelItem[]): string[] { |
|||
const v: string[] = []; |
|||
if (items) { |
|||
items.forEach(item => { |
|||
v.push(item.key); |
|||
}); |
|||
} |
|||
return v; |
|||
} |
|||
|
|||
keyIndex(values: KeyLabelItem[], key: string): number { |
|||
let index = -1; |
|||
if (values) { |
|||
for (let i = 0; i < values.length; i++) { |
|||
if (values[i].key === key) { |
|||
index = i; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
return index; |
|||
} |
|||
|
|||
onSelect(value: KeyLabelItem) { |
|||
if (this.props.form.schema.type === 'array') { |
|||
const v = this.state.currentValue as KeyLabelItem[]; |
|||
v.push(this.keyToCurrentValue(value.key, false) as KeyLabelItem); |
|||
this.setState({ |
|||
currentValue: v |
|||
}); |
|||
this.props.onChangeValidate(this.arrayValues(v)); |
|||
} else { |
|||
this.setState({currentValue: this.keyToCurrentValue(value.key, false)}); |
|||
this.props.onChangeValidate({target: {value: value.key}}); |
|||
} |
|||
} |
|||
|
|||
onDeselect(value: KeyLabelItem) { |
|||
if (this.props.form.schema.type === 'array') { |
|||
const v = this.state.currentValue as KeyLabelItem[]; |
|||
const index = this.keyIndex(v, value.key); |
|||
if (index > -1) { |
|||
v.splice(index, 1); |
|||
} |
|||
this.setState({ |
|||
currentValue: v |
|||
}); |
|||
this.props.onChangeValidate(this.arrayValues(v)); |
|||
} |
|||
} |
|||
|
|||
onBlur() { |
|||
this.setState({ focused: false }); |
|||
} |
|||
|
|||
onFocus() { |
|||
this.setState({ focused: true }); |
|||
} |
|||
|
|||
render() { |
|||
|
|||
let options: React.JSX.Element[] = []; |
|||
if (this.state.items && this.state.items.length > 0) { |
|||
options = this.state.items.map((item) => ( |
|||
<Option key={item.value} value={item.value}>{item.label}</Option> |
|||
)); |
|||
} |
|||
|
|||
let labelClass = 'tb-label'; |
|||
if (this.props.form.required) { |
|||
labelClass += ' tb-required'; |
|||
} |
|||
if (this.props.form.readonly) { |
|||
labelClass += ' tb-readonly'; |
|||
} |
|||
if (this.state.focused) { |
|||
labelClass += ' tb-focused'; |
|||
} |
|||
let mode: Mode; |
|||
let value = this.state.currentValue; |
|||
if (this.props.form.tags || this.props.form.multiple) { |
|||
value = deepClone(value); |
|||
if (this.props.form.tags) { |
|||
mode = 'tags'; |
|||
} else if (this.props.form.multiple) { |
|||
mode = 'multiple'; |
|||
} |
|||
} |
|||
|
|||
const dropdownStyle = {...this.props.form.dropdownStyle, ...{zIndex: 100001}}; |
|||
let dropdownClassName = 'tb-rc-select-dropdown'; |
|||
if (this.props.form.dropdownClassName) { |
|||
dropdownClassName += ' ' + this.props.form.dropdownClassName; |
|||
} |
|||
|
|||
return ( |
|||
<div className='tb-container'> |
|||
<label className={labelClass}>{this.props.form.title}</label> |
|||
<Select |
|||
className={this.props.form.className} |
|||
dropdownClassName={dropdownClassName} |
|||
dropdownStyle={dropdownStyle} |
|||
allowClear={this.props.form.allowClear} |
|||
showSearch={true} |
|||
mode={mode} |
|||
maxTagTextLength={this.props.form.maxTagTextLength} |
|||
disabled={this.props.form.readonly} |
|||
optionLabelProp='children' |
|||
value={value} |
|||
labelInValue={true} |
|||
onSelect={this.onSelect} |
|||
onDeselect={this.onDeselect} |
|||
onFocus={this.onFocus} |
|||
onBlur={this.onBlur} |
|||
placeholder={this.props.form.placeholder} |
|||
style={this.props.form.style || {width: '100%'}}> |
|||
{options} |
|||
</Select> |
|||
<div className='json-form-error' |
|||
style={{opacity: this.props.valid ? '0' : '1', |
|||
bottom: '-5px'}}>{this.props.error}</div> |
|||
</div> |
|||
); |
|||
} |
|||
} |
|||
|
|||
export default ThingsboardBaseComponent(ThingsboardRcSelect); |
|||
@ -1,53 +0,0 @@ |
|||
/* |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
import * as React from 'react'; |
|||
import { createTheme, ThemeProvider } from '@mui/material/styles'; |
|||
import thingsboardTheme from './styles/thingsboardTheme'; |
|||
import ThingsboardSchemaForm from './json-form-schema-form'; |
|||
import { JsonFormProps } from './json-form.models'; |
|||
|
|||
const tbTheme = createTheme(thingsboardTheme); |
|||
|
|||
class ReactSchemaForm extends React.Component<JsonFormProps, {}> { |
|||
|
|||
static defaultProps: JsonFormProps; |
|||
|
|||
constructor(props) { |
|||
super(props); |
|||
} |
|||
|
|||
render() { |
|||
if (this.props.form.length > 0) { |
|||
return <ThemeProvider theme={tbTheme}><ThingsboardSchemaForm {...this.props} /></ThemeProvider>; |
|||
} else { |
|||
return <div></div>; |
|||
} |
|||
} |
|||
} |
|||
|
|||
ReactSchemaForm.defaultProps = { |
|||
isFullscreen: false, |
|||
schema: {}, |
|||
form: ['*'], |
|||
groupInfoes: [], |
|||
option: { |
|||
formDefaults: { |
|||
startEmpty: true |
|||
} |
|||
} |
|||
}; |
|||
|
|||
export default ReactSchemaForm; |
|||
@ -1,217 +0,0 @@ |
|||
/* |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
import * as React from 'react'; |
|||
import JsonFormUtils from './json-form-utils'; |
|||
|
|||
import ThingsboardArray from './json-form-array'; |
|||
import ThingsboardJavaScript from './json-form-javascript'; |
|||
import ThingsboardJson from './json-form-json'; |
|||
import ThingsboardHtml from './json-form-html'; |
|||
import ThingsboardCss from './json-form-css'; |
|||
import ThingsboardColor from './json-form-color'; |
|||
import ThingsboardRcSelect from './json-form-rc-select'; |
|||
import ThingsboardNumber from './json-form-number'; |
|||
import ThingsboardText from './json-form-text'; |
|||
import ThingsboardSelect from './json-form-select'; |
|||
import ThingsboardRadios from './json-form-radios'; |
|||
import ThingsboardDate from './json-form-date'; |
|||
import ThingsboardImage from './json-form-image'; |
|||
import ThingsboardCheckbox from './json-form-checkbox'; |
|||
import ThingsboardHelp from './json-form-help'; |
|||
import ThingsboardFieldSet from './json-form-fieldset'; |
|||
import ThingsboardIcon from './json-form-icon'; |
|||
import { |
|||
JsonFormData, |
|||
JsonFormProps, |
|||
onChangeFn, |
|||
OnColorClickFn, onHelpClickFn, |
|||
OnIconClickFn, |
|||
onToggleFullscreenFn |
|||
} from './json-form.models'; |
|||
|
|||
import _ from 'lodash'; |
|||
import tinycolor from 'tinycolor2'; |
|||
import { GroupInfo } from '@shared/models/widget.models'; |
|||
import ThingsboardMarkdown from '@shared/components/json-form/react/json-form-markdown'; |
|||
import { MouseEvent, ReactNode } from 'react'; |
|||
|
|||
class ThingsboardSchemaForm extends React.Component<JsonFormProps, any> { |
|||
|
|||
private hasConditions: boolean; |
|||
private conditionFunction: Function; |
|||
private readonly mapper: {[type: string]: any}; |
|||
|
|||
constructor(props: JsonFormProps) { |
|||
super(props); |
|||
|
|||
this.mapper = { |
|||
number: ThingsboardNumber, |
|||
text: ThingsboardText, |
|||
password: ThingsboardText, |
|||
textarea: ThingsboardText, |
|||
select: ThingsboardSelect, |
|||
radios: ThingsboardRadios, |
|||
date: ThingsboardDate, |
|||
image: ThingsboardImage, |
|||
checkbox: ThingsboardCheckbox, |
|||
help: ThingsboardHelp, |
|||
array: ThingsboardArray, |
|||
javascript: ThingsboardJavaScript, |
|||
json: ThingsboardJson, |
|||
html: ThingsboardHtml, |
|||
css: ThingsboardCss, |
|||
markdown: ThingsboardMarkdown, |
|||
color: ThingsboardColor, |
|||
'rc-select': ThingsboardRcSelect, |
|||
fieldset: ThingsboardFieldSet, |
|||
icon: ThingsboardIcon |
|||
}; |
|||
|
|||
this.onChange = this.onChange.bind(this); |
|||
this.onColorClick = this.onColorClick.bind(this); |
|||
this.onIconClick = this.onIconClick.bind(this); |
|||
this.onToggleFullscreen = this.onToggleFullscreen.bind(this); |
|||
this.onHelpClick = this.onHelpClick.bind(this); |
|||
this.hasConditions = false; |
|||
} |
|||
|
|||
onChange(key: (string | number)[], val: any, forceUpdate?: boolean) { |
|||
this.props.onModelChange(key, val, forceUpdate); |
|||
if (this.hasConditions) { |
|||
this.forceUpdate(); |
|||
} |
|||
} |
|||
|
|||
onColorClick(key: (string | number)[], val: tinycolor.ColorFormats.RGBA, |
|||
colorSelectedFn: (color: tinycolor.ColorFormats.RGBA) => void) { |
|||
this.props.onColorClick(key, val, colorSelectedFn); |
|||
} |
|||
|
|||
onIconClick(key: (string | number)[], val: string, |
|||
iconSelectedFn: (icon: string) => void) { |
|||
this.props.onIconClick(key, val, iconSelectedFn); |
|||
} |
|||
|
|||
onToggleFullscreen(fullscreenFinishFn?: (el: Element) => void) { |
|||
this.props.onToggleFullscreen(fullscreenFinishFn); |
|||
} |
|||
|
|||
onHelpClick(event: MouseEvent, helpId: string, helpVisibleFn: (visible: boolean) => void, helpReadyFn: (ready: boolean) => void) { |
|||
this.props.onHelpClick(event, helpId, helpVisibleFn, helpReadyFn); |
|||
} |
|||
|
|||
|
|||
builder(form: JsonFormData, |
|||
model: any, |
|||
index: number, |
|||
onChange: onChangeFn, |
|||
onColorClick: OnColorClickFn, |
|||
onIconClick: OnIconClickFn, |
|||
onToggleFullscreen: onToggleFullscreenFn, |
|||
onHelpClick: onHelpClickFn, |
|||
mapper: {[type: string]: any}): React.JSX.Element { |
|||
const type = form.type; |
|||
const Field = this.mapper[type]; |
|||
if (!Field) { |
|||
console.log('Invalid field: \"' + form.key[0] + '\"!'); |
|||
return null; |
|||
} |
|||
if (form.condition) { |
|||
this.hasConditions = true; |
|||
if (!form.conditionFunction) { |
|||
form.conditionFunction = new Function('form', 'model', 'index', `return ${form.condition};`); |
|||
} |
|||
if (form.conditionFunction(form, model, index) === false) { |
|||
return null; |
|||
} |
|||
} |
|||
return <Field model={model} form={form} key={index} onChange={onChange} |
|||
onColorClick={onColorClick} |
|||
onIconClick={onIconClick} |
|||
onToggleFullscreen={onToggleFullscreen} |
|||
onHelpClick={onHelpClick} |
|||
mapper={mapper} builder={this.builder}/>; |
|||
} |
|||
|
|||
createSchema(theForm: any[]): React.JSX.Element { |
|||
const merged = JsonFormUtils.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); |
|||
} |
|||
const forms: ReactNode[] = merged.map(function(form: JsonFormData, index: number) { |
|||
return this.builder(form, this.props.model, index, this.onChange, this.onColorClick, |
|||
this.onIconClick, this.onToggleFullscreen, this.onHelpClick, mapper); |
|||
}.bind(this)); |
|||
|
|||
let formClass = 'SchemaForm'; |
|||
if (this.props.isFullscreen) { |
|||
formClass += ' SchemaFormFullscreen'; |
|||
} |
|||
|
|||
return ( |
|||
<div style={{width: '100%'}} className={formClass}>{forms}</div> |
|||
); |
|||
} |
|||
|
|||
render() { |
|||
if (this.props.groupInfoes && this.props.groupInfoes.length > 0) { |
|||
const content: React.JSX.Element[] = []; |
|||
for (const info of this.props.groupInfoes) { |
|||
const forms = this.createSchema(this.props.form[info.formIndex]); |
|||
const item = <ThingsboardSchemaGroup key={content.length} forms={forms} info={info}></ThingsboardSchemaGroup>; |
|||
content.push(item); |
|||
} |
|||
return (<div>{content}</div>); |
|||
} else { |
|||
return this.createSchema(this.props.form); |
|||
} |
|||
} |
|||
} |
|||
export default ThingsboardSchemaForm; |
|||
|
|||
interface ThingsboardSchemaGroupProps { |
|||
info: GroupInfo; |
|||
forms: React.JSX.Element; |
|||
} |
|||
|
|||
interface ThingsboardSchemaGroupState { |
|||
showGroup: boolean; |
|||
} |
|||
|
|||
class ThingsboardSchemaGroup extends React.Component<ThingsboardSchemaGroupProps, ThingsboardSchemaGroupState> { |
|||
constructor(props: ThingsboardSchemaGroupProps) { |
|||
super(props); |
|||
this.state = { |
|||
showGroup: true |
|||
}; |
|||
} |
|||
|
|||
toogleGroup() { |
|||
this.setState({ |
|||
showGroup: !this.state.showGroup |
|||
}); |
|||
} |
|||
|
|||
render() { |
|||
const theCla = 'pull-right fa fa-chevron-down tb-toggle-icon' + (this.state.showGroup ? '' : ' tb-toggled'); |
|||
return (<section className='mat-elevation-z1' style={{marginTop: '10px'}}> |
|||
<div className='SchemaGroupname tb-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>); |
|||
} |
|||
} |
|||
@ -1,93 +0,0 @@ |
|||
/* |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
import * as React from 'react'; |
|||
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; |
|||
import MenuItem from '@mui/material/MenuItem'; |
|||
import FormControl from '@mui/material/FormControl'; |
|||
import InputLabel from '@mui/material/InputLabel'; |
|||
import Select, { SelectChangeEvent } from '@mui/material/Select'; |
|||
import ThingsboardBaseComponent from '@shared/components/json-form/react/json-form-base-component'; |
|||
import { isObject } from '@core/utils'; |
|||
|
|||
interface ThingsboardSelectState extends JsonFormFieldState { |
|||
currentValue: any; |
|||
} |
|||
|
|||
class ThingsboardSelect extends React.Component<JsonFormFieldProps, ThingsboardSelectState> { |
|||
|
|||
static getDerivedStateFromProps(props: JsonFormFieldProps) { |
|||
if (props.model && props.form.key) { |
|||
return { |
|||
currentValue: ThingsboardSelect.getModelKey(props.model, props.form.key) |
|||
|| (props.form.titleMap != null ? props.form.titleMap[0].value : '') |
|||
} |
|||
} |
|||
} |
|||
|
|||
static getModelKey(model: any, key: (string | number)[]) { |
|||
if (Array.isArray(key)) { |
|||
const res = key.reduce((cur, nxt) => (cur[nxt] || {}), model); |
|||
if (res && isObject(res)) { |
|||
return undefined; |
|||
} else { |
|||
return res; |
|||
} |
|||
} else { |
|||
return model[key]; |
|||
} |
|||
} |
|||
|
|||
constructor(props: JsonFormFieldProps) { |
|||
super(props); |
|||
this.onSelected = this.onSelected.bind(this); |
|||
const possibleValue = ThingsboardSelect.getModelKey(this.props.model, this.props.form.key); |
|||
this.state = { |
|||
currentValue: this.props.model !== undefined && possibleValue ? possibleValue : this.props.form.titleMap != null ? |
|||
this.props.form.titleMap[0].value : '' |
|||
}; |
|||
} |
|||
|
|||
onSelected(event: SelectChangeEvent<any>) { |
|||
|
|||
this.setState({ |
|||
currentValue: event.target.value |
|||
}); |
|||
this.props.onChangeValidate(event); |
|||
} |
|||
|
|||
render() { |
|||
const menuItems = this.props.form.titleMap.map((item, idx) => ( |
|||
<MenuItem key={idx} |
|||
value={item.value}>{item.name}</MenuItem> |
|||
)); |
|||
|
|||
return ( |
|||
<FormControl className={this.props.form.htmlClass} |
|||
disabled={this.props.form.readonly} |
|||
fullWidth={true}> |
|||
<InputLabel variant={'standard'} htmlFor='select-field'>{this.props.form.title}</InputLabel> |
|||
<Select |
|||
variant={'standard'} |
|||
value={this.state.currentValue} |
|||
onChange={this.onSelected}> |
|||
{menuItems} |
|||
</Select> |
|||
</FormControl> |
|||
); |
|||
} |
|||
} |
|||
|
|||
export default ThingsboardBaseComponent(ThingsboardSelect); |
|||
@ -1,92 +0,0 @@ |
|||
/* |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
import * as React from 'react'; |
|||
import ThingsboardBaseComponent from './json-form-base-component'; |
|||
import TextField from '@mui/material/TextField'; |
|||
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; |
|||
|
|||
interface ThingsboardTextState extends JsonFormFieldState { |
|||
focused: boolean; |
|||
} |
|||
|
|||
class ThingsboardText extends React.Component<JsonFormFieldProps, ThingsboardTextState> { |
|||
|
|||
constructor(props: JsonFormFieldProps) { |
|||
super(props); |
|||
this.onBlur = this.onBlur.bind(this); |
|||
this.onFocus = this.onFocus.bind(this); |
|||
this.state = { |
|||
focused: false |
|||
}; |
|||
} |
|||
|
|||
onBlur() { |
|||
this.setState({focused: false}); |
|||
} |
|||
|
|||
onFocus() { |
|||
this.setState({focused: true}); |
|||
} |
|||
|
|||
render() { |
|||
|
|||
let fieldClass = 'tb-field'; |
|||
if (this.props.form.required) { |
|||
fieldClass += ' tb-required'; |
|||
} |
|||
if (this.props.form.readonly) { |
|||
fieldClass += ' tb-readonly'; |
|||
} |
|||
if (this.state.focused) { |
|||
fieldClass += ' tb-focused'; |
|||
} |
|||
|
|||
const multiline = this.props.form.type === 'textarea'; |
|||
let rows = 1; |
|||
let rowsMax = 1; |
|||
let minHeight = 48; |
|||
if (multiline) { |
|||
rows = this.props.form.rows || 2; |
|||
rowsMax = this.props.form.rowsMax; |
|||
minHeight = 19 * rows + 48; |
|||
} |
|||
|
|||
return ( |
|||
<div> |
|||
<TextField |
|||
variant={'standard'} |
|||
className={fieldClass} |
|||
type={this.props.form.type} |
|||
label={this.props.form.title} |
|||
multiline={multiline} |
|||
error={!this.props.valid} |
|||
helperText={this.props.valid ? this.props.form.placeholder : this.props.error} |
|||
onChange={(e) => { |
|||
this.props.onChangeValidate(e); |
|||
}} |
|||
defaultValue={this.props.value} |
|||
disabled={this.props.form.readonly} |
|||
rows={rows} |
|||
maxRows={rowsMax} |
|||
onFocus={this.onFocus} |
|||
onBlur={this.onBlur} |
|||
style={this.props.form.style || {width: '100%', minHeight: minHeight + 'px'}}/> |
|||
</div> |
|||
); |
|||
} |
|||
} |
|||
|
|||
export default ThingsboardBaseComponent(ThingsboardText); |
|||
@ -1,141 +0,0 @@ |
|||
///
|
|||
/// Copyright © 2016-2024 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import tinycolor from 'tinycolor2'; |
|||
import { GroupInfo } from '@shared/models/widget.models'; |
|||
import { MouseEvent } from 'react'; |
|||
|
|||
export interface SchemaValidationResult { |
|||
valid: boolean; |
|||
error?: { |
|||
message?: string; |
|||
}; |
|||
} |
|||
|
|||
export interface FormOption { |
|||
formDefaults?: { |
|||
startEmpty?: boolean; |
|||
readonly?: boolean; |
|||
}; |
|||
supressPropertyTitles?: boolean; |
|||
} |
|||
|
|||
export interface DefaultsFormOptions { |
|||
global?: FormOption; |
|||
required?: boolean; |
|||
path?: string[]; |
|||
lookup?: {[key: string]: any}; |
|||
ignore?: {[key: string]: boolean}; |
|||
} |
|||
|
|||
export type onChangeFn = (key: (string | number)[], val: any, forceUpdate?: boolean) => void; |
|||
export type OnColorClickFn = (key: (string | number)[], val: tinycolor.ColorFormats.RGBA, |
|||
colorSelectedFn: (color: tinycolor.ColorFormats.RGBA) => void) => void; |
|||
export type OnIconClickFn = (key: (string | number)[], val: string, |
|||
iconSelectedFn: (icon: string) => void) => void; |
|||
export type onToggleFullscreenFn = (fullscreenFinishFn?: (el: Element) => void) => void; |
|||
export type onHelpClickFn = (event: MouseEvent, helpId: string, helpVisibleFn: (visible: boolean) => void, |
|||
helpReadyFn: (ready: boolean) => void) => void; |
|||
|
|||
export interface JsonFormProps { |
|||
model?: any; |
|||
schema?: any; |
|||
form?: any; |
|||
groupInfoes?: GroupInfo[]; |
|||
isFullscreen: boolean; |
|||
ignore?: {[key: string]: boolean}; |
|||
option: FormOption; |
|||
onModelChange?: onChangeFn; |
|||
onColorClick?: OnColorClickFn; |
|||
onIconClick?: OnIconClickFn; |
|||
onToggleFullscreen?: onToggleFullscreenFn; |
|||
onHelpClick?: onHelpClickFn; |
|||
mapper?: {[type: string]: any}; |
|||
} |
|||
|
|||
export interface KeyLabelItem { |
|||
key: string; |
|||
label: string; |
|||
value?: string; |
|||
} |
|||
|
|||
export interface JsonSchemaData { |
|||
type: string; |
|||
default: any; |
|||
items?: JsonSchemaData; |
|||
properties?: any; |
|||
} |
|||
|
|||
export interface JsonFormData { |
|||
type: string; |
|||
key: (string | number)[]; |
|||
title: string; |
|||
readonly: boolean; |
|||
required: boolean; |
|||
default?: any; |
|||
condition?: string; |
|||
conditionFunction?: Function; |
|||
style?: any; |
|||
rows?: number; |
|||
rowsMax?: number; |
|||
placeholder?: string; |
|||
schema: JsonSchemaData; |
|||
titleMap: { |
|||
value: any; |
|||
name: string; |
|||
}[]; |
|||
items?: Array<KeyLabelItem> | Array<JsonFormData>; |
|||
tabs?: Array<JsonFormData>; |
|||
tags?: any; |
|||
helpId?: string; |
|||
startEmpty?: boolean; |
|||
[key: string]: any; |
|||
} |
|||
|
|||
export type ComponentBuilderFn = (form: JsonFormData, |
|||
model: any, |
|||
index: number, |
|||
onChange: onChangeFn, |
|||
onColorClick: OnColorClickFn, |
|||
onIconClick: OnIconClickFn, |
|||
onToggleFullscreen: onToggleFullscreenFn, |
|||
onHelpClick: onHelpClickFn, |
|||
mapper: {[type: string]: any}) => JSX.Element; |
|||
|
|||
export interface JsonFormFieldProps { |
|||
value: any; |
|||
model: any; |
|||
form: JsonFormData; |
|||
builder: ComponentBuilderFn; |
|||
mapper?: {[type: string]: any}; |
|||
onChange?: onChangeFn; |
|||
onColorClick?: OnColorClickFn; |
|||
onIconClick?: OnIconClickFn; |
|||
onChangeValidate?: (e: any, forceUpdate?: boolean) => void; |
|||
onToggleFullscreen?: onToggleFullscreenFn; |
|||
onHelpClick?: onHelpClickFn; |
|||
valid?: boolean; |
|||
error?: string; |
|||
options?: { |
|||
setSchemaDefaults?: boolean; |
|||
}; |
|||
} |
|||
|
|||
export interface JsonFormFieldState { |
|||
value?: any; |
|||
valid?: boolean; |
|||
error?: string; |
|||
} |
|||
@ -1,361 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
$swift-ease-out-duration: .4s !default; |
|||
$swift-ease-out-timing-function: cubic-bezier(.25, .8, .25, 1) !default; |
|||
|
|||
$input-label-float-offset: 6px !default; |
|||
$input-label-float-scale: .75 !default; |
|||
|
|||
$previewSize: 100px !default; |
|||
|
|||
.tb-json-form { |
|||
|
|||
&.tb-fullscreen { |
|||
background: #fff; |
|||
position: absolute; |
|||
top: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
left: 0; |
|||
> div.fullscreen-form-field { |
|||
position: relative; |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
} |
|||
|
|||
.json-form-error { |
|||
position: relative; |
|||
bottom: -5px; |
|||
font-size: 12px; |
|||
line-height: 12px; |
|||
color: rgb(244, 67, 54); |
|||
|
|||
transition: all 450ms cubic-bezier(.23, 1, .32, 1) 0ms; |
|||
} |
|||
|
|||
.tb-container { |
|||
position: relative; |
|||
box-sizing: border-box; |
|||
padding: 10px 0; |
|||
margin-top: 32px; |
|||
} |
|||
|
|||
.tb-field { |
|||
padding-bottom: 18px; |
|||
|
|||
.MuiInputBase-multiline { |
|||
flex: 1; |
|||
flex-direction: column; |
|||
.MuiInputBase-inputMultiline { |
|||
flex: 1; |
|||
} |
|||
} |
|||
|
|||
&.tb-required { |
|||
label::after { |
|||
font-size: 13px; |
|||
color: rgba(0, 0, 0, .54); |
|||
vertical-align: top; |
|||
content: " *"; |
|||
} |
|||
} |
|||
|
|||
&.tb-focused:not(.tb-readonly) { |
|||
label::after { |
|||
color: rgb(221, 44, 0); |
|||
} |
|||
} |
|||
} |
|||
|
|||
.tb-date-field { |
|||
&.tb-required { |
|||
div > div:first-child::after { |
|||
font-size: 13px; |
|||
color: rgba(0, 0, 0, .54); |
|||
vertical-align: top; |
|||
content: " *"; |
|||
} |
|||
} |
|||
|
|||
&.tb-focused:not(.tb-readonly) { |
|||
div > div:first-child::after { |
|||
color: rgb(221, 44, 0); |
|||
} |
|||
} |
|||
} |
|||
|
|||
label.tb-label { |
|||
position: absolute; |
|||
right: auto; |
|||
bottom: 100%; |
|||
left: 0; |
|||
color: rgba(0, 0, 0, .54); |
|||
|
|||
transition: transform $swift-ease-out-timing-function $swift-ease-out-duration, width $swift-ease-out-timing-function $swift-ease-out-duration; |
|||
|
|||
transform: translate3d(0, $input-label-float-offset, 0) scale($input-label-float-scale); |
|||
transform-origin: left top; |
|||
-webkit-font-smoothing: antialiased; |
|||
|
|||
&.tb-focused { |
|||
color: rgb(96, 125, 139); |
|||
} |
|||
|
|||
&.tb-required::after { |
|||
font-size: 13px; |
|||
color: rgba(0, 0, 0, .54); |
|||
vertical-align: top; |
|||
content: " *"; |
|||
} |
|||
|
|||
&.tb-focused:not(.tb-readonly)::after { |
|||
color: rgb(221, 44, 0); |
|||
} |
|||
} |
|||
|
|||
.tb-head-label { |
|||
color: rgba(0, 0, 0, .54); |
|||
padding-bottom: 15px; |
|||
} |
|||
|
|||
.SchemaGroupname { |
|||
padding: 10px 20px; |
|||
background-color: #f1f1f1; |
|||
} |
|||
|
|||
.invisible { |
|||
display: none; |
|||
} |
|||
|
|||
.tb-button-toggle .tb-toggle-icon { |
|||
display: inline-block; |
|||
width: 15px; |
|||
margin: auto 0 auto auto; |
|||
background-size: 100% auto; |
|||
|
|||
transition: transform .3s, ease-in-out; |
|||
} |
|||
|
|||
.tb-button-toggle .tb-toggle-icon.tb-toggled { |
|||
transform: rotateZ(180deg); |
|||
} |
|||
|
|||
.fullscreen-form-field { |
|||
.json-form-ace-editor { |
|||
height: calc(100% - 60px); |
|||
} |
|||
} |
|||
|
|||
.json-form-ace-editor { |
|||
position: relative; |
|||
height: 100%; |
|||
border: 1px solid #c0c0c0; |
|||
|
|||
.title-panel { |
|||
position: absolute; |
|||
top: 10px; |
|||
right: 20px; |
|||
z-index: 5; |
|||
font-size: .8rem; |
|||
font-weight: 500; |
|||
|
|||
label { |
|||
padding: 4px; |
|||
color: #00acc1; |
|||
background: rgba(220, 220, 220, .35); |
|||
border-radius: 5px; |
|||
} |
|||
|
|||
button.tidy-button { |
|||
background: rgba(220, 220, 220, .35) !important; |
|||
|
|||
span { |
|||
padding: 0 !important; |
|||
font-size: 12px !important; |
|||
} |
|||
} |
|||
button.help-button { |
|||
background: rgba(220, 220, 220, .35); |
|||
padding: 4px; |
|||
} |
|||
div.help-button-loading { |
|||
pointer-events: none; |
|||
background: #f3f3f3; |
|||
border-radius: 50%; |
|||
display: flex; |
|||
place-content: center; |
|||
align-items: center; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.tb-image-select-container { |
|||
position: relative; |
|||
width: 100%; |
|||
height: $previewSize; |
|||
} |
|||
|
|||
.tb-image-preview { |
|||
width: auto; |
|||
max-width: $previewSize; |
|||
height: auto; |
|||
max-height: $previewSize; |
|||
} |
|||
|
|||
.tb-image-preview-container { |
|||
position: relative; |
|||
float: left; |
|||
width: $previewSize; |
|||
height: $previewSize; |
|||
margin-right: 12px; |
|||
vertical-align: top; |
|||
border: solid 1px; |
|||
|
|||
div { |
|||
width: 100%; |
|||
font-size: 18px; |
|||
text-align: center; |
|||
} |
|||
|
|||
div, .tb-image-preview { |
|||
position: absolute; |
|||
top: 50%; |
|||
left: 50%; |
|||
transform: translate(-50%, -50%); |
|||
} |
|||
} |
|||
|
|||
.tb-dropzone { |
|||
outline: none; |
|||
position: relative; |
|||
height: $previewSize; |
|||
padding: 0 8px; |
|||
overflow: hidden; |
|||
vertical-align: top; |
|||
border: dashed 2px; |
|||
|
|||
div { |
|||
position: absolute; |
|||
top: 50%; |
|||
left: 50%; |
|||
width: 100%; |
|||
font-size: 24px; |
|||
text-align: center; |
|||
transform: translate(-50%, -50%); |
|||
} |
|||
} |
|||
|
|||
.tb-image-clear-container { |
|||
position: relative; |
|||
float: right; |
|||
width: 48px; |
|||
height: $previewSize; |
|||
} |
|||
|
|||
.tb-image-clear-btn { |
|||
position: absolute !important; |
|||
top: 50%; |
|||
transform: translate(0%, -50%) !important; |
|||
} |
|||
|
|||
.MuiButton-root { |
|||
text-transform: none; |
|||
} |
|||
|
|||
} |
|||
|
|||
.rc-select { |
|||
box-sizing: border-box; |
|||
display: inline-block; |
|||
position: relative; |
|||
vertical-align: middle; |
|||
color: #666; |
|||
line-height: 28px; |
|||
font-size: inherit !important; |
|||
.rc-select-selector { |
|||
outline: none; |
|||
user-select: none; |
|||
box-sizing: border-box; |
|||
display: block; |
|||
background-color: #fff; |
|||
border-radius: 6px; |
|||
} |
|||
&.rc-select-single { |
|||
&:not(.rc-select-customize-input) { |
|||
.rc-select-selector { |
|||
height: 28px; |
|||
line-height: 28px; |
|||
position: relative; |
|||
border: 1px solid #d9d9d9; |
|||
&:hover { |
|||
border-color: #23c0fa; |
|||
box-shadow: 0 0 2px rgba(45, 183, 245, 0.8); |
|||
} |
|||
.rc-select-selection-search { |
|||
.rc-select-selection-search-input { |
|||
cursor: pointer; |
|||
background: transparent; |
|||
margin-left: 10px; |
|||
} |
|||
} |
|||
.rc-select-selection-item, .rc-select-selection-placeholder { |
|||
top: 0; |
|||
left: 10px; |
|||
} |
|||
} |
|||
&.rc-select-focused { |
|||
.rc-select-selector { |
|||
border-color: #23c0fa !important; |
|||
box-shadow: 0 0 2px rgba(45, 183, 245, 0.8) !important; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.rc-select-dropdown { |
|||
&.tb-rc-select-dropdown { |
|||
z-index: 100001; |
|||
background-color: white; |
|||
border: 1px solid #d9d9d9; |
|||
box-shadow: 0 0 4px #d9d9d9; |
|||
border-radius: 4px; |
|||
box-sizing: border-box; |
|||
outline: none; |
|||
|
|||
.rc-select-item { |
|||
&.rc-select-item-option { |
|||
margin: 0; |
|||
position: relative; |
|||
display: block; |
|||
padding: 7px 10px; |
|||
font-weight: normal; |
|||
color: #666; |
|||
white-space: nowrap; |
|||
&.rc-select-item-option-selected { |
|||
color: #666; |
|||
background-color: #ddd; |
|||
} |
|||
&.rc-select-item-option-active { |
|||
background-color: #5897fb; |
|||
color: white; |
|||
cursor: pointer; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,46 +0,0 @@ |
|||
///
|
|||
/// Copyright © 2016-2024 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { indigo, deepOrange } from '@mui/material/colors'; |
|||
import { ThemeOptions } from '@mui/material/styles'; |
|||
import { PaletteOptions } from '@mui/material/styles/createPalette'; |
|||
import { mergeDeep } from '@core/utils'; |
|||
|
|||
const PRIMARY_COLOR = '#305680'; |
|||
const SECONDARY_COLOR = '#527dad'; |
|||
const HUE3_COLOR = '#a7c1de'; |
|||
|
|||
const tbIndigo = mergeDeep<any>({}, indigo, { |
|||
500: PRIMARY_COLOR, |
|||
600: SECONDARY_COLOR, |
|||
700: PRIMARY_COLOR, |
|||
A100: HUE3_COLOR |
|||
}); |
|||
|
|||
const thingsboardPalette: PaletteOptions = { |
|||
primary: tbIndigo, |
|||
secondary: deepOrange, |
|||
background: { |
|||
default: '#eee' |
|||
} |
|||
}; |
|||
|
|||
export default { |
|||
typography: { |
|||
fontFamily: 'Roboto, \'Helvetica Neue\', sans-serif' |
|||
}, |
|||
palette: thingsboardPalette, |
|||
} as ThemeOptions; |
|||
@ -0,0 +1,88 @@ |
|||
///
|
|||
/// Copyright © 2016-2024 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
export interface FormOption { |
|||
formDefaults?: { |
|||
startEmpty?: boolean; |
|||
readonly?: boolean; |
|||
}; |
|||
supressPropertyTitles?: boolean; |
|||
} |
|||
|
|||
export interface DefaultsFormOptions { |
|||
global?: FormOption; |
|||
required?: boolean; |
|||
path?: string[]; |
|||
lookup?: {[key: string]: any}; |
|||
ignore?: {[key: string]: boolean}; |
|||
} |
|||
|
|||
export interface KeyLabelItem { |
|||
key: string; |
|||
label: string; |
|||
value?: string; |
|||
} |
|||
|
|||
export interface JsonSchemaData { |
|||
type: string; |
|||
default: any; |
|||
items?: JsonSchemaData; |
|||
properties?: any; |
|||
} |
|||
|
|||
export interface JsonFormData { |
|||
type: string; |
|||
key: (string | number)[]; |
|||
title: string; |
|||
readonly: boolean; |
|||
required: boolean; |
|||
default?: any; |
|||
condition?: string; |
|||
conditionFunction?: Function; |
|||
style?: any; |
|||
rows?: number; |
|||
rowsMax?: number; |
|||
placeholder?: string; |
|||
schema: JsonSchemaData; |
|||
titleMap: { |
|||
value: any; |
|||
name: string; |
|||
}[]; |
|||
items?: Array<KeyLabelItem> | Array<JsonFormData>; |
|||
tabs?: Array<JsonFormData>; |
|||
tags?: any; |
|||
helpId?: string; |
|||
startEmpty?: boolean; |
|||
[key: string]: any; |
|||
} |
|||
|
|||
export interface GroupInfo { |
|||
formIndex: number; |
|||
GroupTitle: string; |
|||
} |
|||
|
|||
export interface JsonSchema { |
|||
type: string; |
|||
title?: string; |
|||
properties: {[key: string]: any}; |
|||
required?: string[]; |
|||
} |
|||
|
|||
export interface JsonSettingsSchema { |
|||
schema?: JsonSchema; |
|||
form?: any[]; |
|||
groupInfoes?: GroupInfo[]; |
|||
} |
|||
Loading…
Reference in new issue