mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
feat: implement disabled, immutable and generate (#2280)
* feat: implement `disabled`, `immutable` and `generate` * chore: remove unnecessary code * chore: add generate to textarea and implement immutable * no generate for textarea --------- Co-authored-by: Matt Hill <matthewonthemoon@gmail.com>
This commit is contained in:
committed by
Aiden McClelland
parent
4d1c7a3884
commit
8aa19e6420
91
frontend/package-lock.json
generated
91
frontend/package-lock.json
generated
@@ -22,17 +22,14 @@
|
||||
"@maskito/angular": "^0.10.0",
|
||||
"@maskito/core": "^0.10.0",
|
||||
"@materia-ui/ngx-monaco-editor": "^6.0.0",
|
||||
"@ng-web-apis/common": "^2.0.0",
|
||||
"@ng-web-apis/mutation-observer": "^2.0.0",
|
||||
"@ng-web-apis/resize-observer": "^2.0.0",
|
||||
"@start9labs/argon2": "^0.1.0",
|
||||
"@start9labs/emver": "^0.1.5",
|
||||
"@start9labs/start-sdk": "0.4.0-rev0.lib0.rc4",
|
||||
"@taiga-ui/addon-charts": "3.27.0",
|
||||
"@taiga-ui/cdk": "3.27.0",
|
||||
"@taiga-ui/core": "3.27.0",
|
||||
"@taiga-ui/icons": "3.27.0",
|
||||
"@taiga-ui/kit": "3.27.0",
|
||||
"@start9labs/start-sdk": "0.4.0-rev0.lib0.rc5",
|
||||
"@taiga-ui/addon-charts": "3.28.0",
|
||||
"@taiga-ui/cdk": "3.28.0",
|
||||
"@taiga-ui/core": "3.28.0",
|
||||
"@taiga-ui/icons": "3.28.0",
|
||||
"@taiga-ui/kit": "3.28.0",
|
||||
"angular-svg-round-progressbar": "^9.0.0",
|
||||
"ansi-to-html": "^0.7.2",
|
||||
"base64-js": "^1.5.1",
|
||||
@@ -97,14 +94,14 @@
|
||||
"uuid": "8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "18.15.0",
|
||||
"@types/node": "16.4.13",
|
||||
"@types/uuid": "8.3.1",
|
||||
"husky": "^4.3.8",
|
||||
"lint-staged": "^13.2.0",
|
||||
"prettier": "^2.8.4",
|
||||
"ts-node": "10.9.0",
|
||||
"lint-staged": "^12.3.7",
|
||||
"prettier": "^2.6.1",
|
||||
"ts-node": "10.2.0",
|
||||
"tslint": "6.1.3",
|
||||
"typescript": "4.8.4"
|
||||
"typescript": "4.3.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rxjs": ">=7.0.0"
|
||||
@@ -3831,9 +3828,9 @@
|
||||
"integrity": "sha512-1dhiG03VkfEwSLx/JPKVms6srAbYFQgwfSGhwpUKMDliMXuAHGVaueStmqzVxn3JpH/HEVz0QW8w/PXHqjdiIg=="
|
||||
},
|
||||
"node_modules/@start9labs/start-sdk": {
|
||||
"version": "0.4.0-rev0.lib0.rc4",
|
||||
"resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-rev0.lib0.rc4.tgz",
|
||||
"integrity": "sha512-ouOpplLYCFFj5PutVtN5S0TI1UsdXA5nauOrFqKvLyhxazGjHprCpQ/fVHNSspPl/+ujNb7upzFxdZnWOE2oXw==",
|
||||
"version": "0.4.0-rev0.lib0.rc5",
|
||||
"resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-rev0.lib0.rc5.tgz",
|
||||
"integrity": "sha512-2hAJE1id0VgpU8DJt/I+m/IEePmnspzF8BxUoLO3C+ZgyOZU1tEri1f9QCsS6OLn3J11xPlpY1VuSjP5CyHC+Q==",
|
||||
"dependencies": {
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"ts-matches": "^5.4.1",
|
||||
@@ -3861,9 +3858,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/addon-charts": {
|
||||
"version": "3.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-3.27.0.tgz",
|
||||
"integrity": "sha512-PZMwRl8pcbF1UcRXzrnzF6rcdg6ZMHSdiF7Q2VUO8Q39GFguyYNYIFdkRHOLvh1wbsXQKoSxho72RN2yeEybCA==",
|
||||
"version": "3.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-3.28.0.tgz",
|
||||
"integrity": "sha512-ZLsOKrEfni8T+ppteJLULooRqtmvP8aZ0cf7WUEEjEeNR05out6eh8a3uHsnx241HI/or8b4OVKHbTmiFm9Mzg==",
|
||||
"dependencies": {
|
||||
"tslib": ">=2.0.0"
|
||||
},
|
||||
@@ -3871,22 +3868,22 @@
|
||||
"@angular/common": ">=12.0.0",
|
||||
"@angular/core": ">=12.0.0",
|
||||
"@ng-web-apis/common": ">=2.0.0",
|
||||
"@taiga-ui/cdk": ">=3.27.0",
|
||||
"@taiga-ui/core": ">=3.27.0",
|
||||
"@taiga-ui/cdk": ">=3.28.0",
|
||||
"@taiga-ui/core": ">=3.28.0",
|
||||
"@tinkoff/ng-polymorpheus": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/cdk": {
|
||||
"version": "3.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-3.27.0.tgz",
|
||||
"integrity": "sha512-53XLDaQzStpjTV7a4X8658YVlaG7bp1JG4cgIamexylXwkWdsHa9o9KnFFOgsGO5I7heiQ2+kotKPWg7sgUwuQ==",
|
||||
"version": "3.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-3.28.0.tgz",
|
||||
"integrity": "sha512-U9LTaiaHABanwxssPyutqiK1I8aUKX8ZpJ3CpMvhxszHC3zMYp4/N3RvxYfI8Mb2sqeLR8D+x85EElbWQIxRkA==",
|
||||
"dependencies": {
|
||||
"@ng-web-apis/common": "2.1.0",
|
||||
"@ng-web-apis/mutation-observer": "2.0.0",
|
||||
"@ng-web-apis/resize-observer": "2.0.0",
|
||||
"@tinkoff/ng-event-plugins": "3.1.0",
|
||||
"@tinkoff/ng-polymorpheus": "4.1.0",
|
||||
"tslib": "2.5.0"
|
||||
"tslib": "2.5.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"ng-morph": "2.2.4",
|
||||
@@ -3901,11 +3898,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/core": {
|
||||
"version": "3.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-3.27.0.tgz",
|
||||
"integrity": "sha512-kXODpMjhxR+4YcdEFVpVaC++G7scMCSuSKPuXXoOCWtEZsQTp/pvSCCxcg951/lLRyh0MkzvEHyz7a8BKikgog==",
|
||||
"version": "3.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-3.28.0.tgz",
|
||||
"integrity": "sha512-7P62xmja4kpEwVe43zgMfSg1UmYzkdMjNr4DF1S1zU8u0gKQGYHcUFQL1hqTJk6W50xSXVyi4tlWKKCXMvEd5Q==",
|
||||
"dependencies": {
|
||||
"@taiga-ui/i18n": "^3.27.0",
|
||||
"@taiga-ui/i18n": "^3.28.0",
|
||||
"tslib": ">=2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@@ -3917,17 +3914,17 @@
|
||||
"@angular/router": ">=12.0.0",
|
||||
"@ng-web-apis/common": ">=2.0.0",
|
||||
"@ng-web-apis/mutation-observer": ">=2.0.0",
|
||||
"@taiga-ui/cdk": ">=3.27.0",
|
||||
"@taiga-ui/i18n": ">=3.27.0",
|
||||
"@taiga-ui/cdk": ">=3.28.0",
|
||||
"@taiga-ui/i18n": ">=3.28.0",
|
||||
"@tinkoff/ng-event-plugins": ">=3.1.0",
|
||||
"@tinkoff/ng-polymorpheus": ">=4.0.0",
|
||||
"rxjs": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/i18n": {
|
||||
"version": "3.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-3.27.0.tgz",
|
||||
"integrity": "sha512-orOoo4CeecBc4GVMFcMhwvYo83wsudgtbnEbmFecE2NZO3wdntjOGE/TNpVM28JinO3uL5yabgDTd3UaxK6NSw==",
|
||||
"version": "3.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-3.28.0.tgz",
|
||||
"integrity": "sha512-wf+BNATdoXhL5MnriF6iQ6xZtCGkD8wTwzosf4zsh7cGoDAPuV5pVWQRVRGGHgjE6gVJhSDdhCBGiM0QqdM3TQ==",
|
||||
"dependencies": {
|
||||
"tslib": ">=2.0.0"
|
||||
},
|
||||
@@ -3937,17 +3934,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/icons": {
|
||||
"version": "3.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-3.27.0.tgz",
|
||||
"integrity": "sha512-uXMe4B3cMgJ1qLfezsrOxvsHD9Bw6y39921GFMvlpeIwSEnXMc/rn1wEQpyd6Qo1Ib9AfFWHRDhBa7NPGnXllA==",
|
||||
"version": "3.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-3.28.0.tgz",
|
||||
"integrity": "sha512-TzQEKgRLP5f+wGsDLMqnBUYPhCN/jgRzQbOWZPIrl+CzaYQTbsFRo1YlKEfMO3Wk55R8QBKv0qpj35+i2Q8Mmg==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/kit": {
|
||||
"version": "3.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-3.27.0.tgz",
|
||||
"integrity": "sha512-2YYiku5wXCr1XeqZHnOgLTH4o3rW3EsCx5O8FRSy2LCtkGFLfLemV7E8x1WQqYzOlTW7cCa2goo+K1NMrUWfMQ==",
|
||||
"version": "3.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-3.28.0.tgz",
|
||||
"integrity": "sha512-jLi/mmIS7kqG1FEY7LT+1uH76pEAiWZsZEQH+3rOwvEGaQBjLE73OPf83f/swaYtFm/DgJemMNnfEMYu661DYA==",
|
||||
"dependencies": {
|
||||
"@maskito/angular": "0.11.1",
|
||||
"@maskito/core": "0.11.1",
|
||||
@@ -3964,9 +3961,9 @@
|
||||
"@ng-web-apis/common": ">=2.0.0",
|
||||
"@ng-web-apis/mutation-observer": ">=2.0.0",
|
||||
"@ng-web-apis/resize-observer": ">=2.0.0",
|
||||
"@taiga-ui/cdk": ">=3.27.0",
|
||||
"@taiga-ui/core": ">=3.27.0",
|
||||
"@taiga-ui/i18n": ">=3.27.0",
|
||||
"@taiga-ui/cdk": ">=3.28.0",
|
||||
"@taiga-ui/core": ">=3.28.0",
|
||||
"@taiga-ui/i18n": ">=3.28.0",
|
||||
"@tinkoff/ng-polymorpheus": ">=4.0.0",
|
||||
"rxjs": ">=6.0.0"
|
||||
}
|
||||
@@ -14517,9 +14514,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
|
||||
"integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.2.tgz",
|
||||
"integrity": "sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA=="
|
||||
},
|
||||
"node_modules/tslint": {
|
||||
"version": "6.1.3",
|
||||
|
||||
@@ -47,16 +47,13 @@
|
||||
"@maskito/angular": "^0.10.0",
|
||||
"@maskito/core": "^0.10.0",
|
||||
"@materia-ui/ngx-monaco-editor": "^6.0.0",
|
||||
"@ng-web-apis/common": "^2.0.0",
|
||||
"@ng-web-apis/mutation-observer": "^2.0.0",
|
||||
"@ng-web-apis/resize-observer": "^2.0.0",
|
||||
"@start9labs/argon2": "^0.1.0",
|
||||
"@start9labs/emver": "^0.1.5",
|
||||
"@taiga-ui/addon-charts": "3.27.0",
|
||||
"@taiga-ui/cdk": "3.27.0",
|
||||
"@taiga-ui/core": "3.27.0",
|
||||
"@taiga-ui/icons": "3.27.0",
|
||||
"@taiga-ui/kit": "3.27.0",
|
||||
"@taiga-ui/addon-charts": "3.28.0",
|
||||
"@taiga-ui/cdk": "3.28.0",
|
||||
"@taiga-ui/core": "3.28.0",
|
||||
"@taiga-ui/icons": "3.28.0",
|
||||
"@taiga-ui/kit": "3.28.0",
|
||||
"angular-svg-round-progressbar": "^9.0.0",
|
||||
"ansi-to-html": "^0.7.2",
|
||||
"base64-js": "^1.5.1",
|
||||
@@ -78,7 +75,7 @@
|
||||
"patch-db-client": "file: ../../../patch-db/client",
|
||||
"pbkdf2": "^3.1.2",
|
||||
"rxjs": "^7.5.6",
|
||||
"@start9labs/start-sdk": "0.4.0-rev0.lib0.rc4",
|
||||
"@start9labs/start-sdk": "0.4.0-rev0.lib0.rc5",
|
||||
"swiper": "^8.2.4",
|
||||
"ts-matches": "^5.2.1",
|
||||
"tslib": "^2.3.0",
|
||||
|
||||
@@ -62,6 +62,7 @@ export * from './util/base-64'
|
||||
export * from './util/copy-to-clipboard'
|
||||
export * from './util/get-new-entries'
|
||||
export * from './util/get-pkg-id'
|
||||
export * from './util/invert'
|
||||
export * from './util/misc.util'
|
||||
export * from './util/rpc.util'
|
||||
export * from './util/to-local-iso-string'
|
||||
|
||||
12
frontend/projects/shared/src/util/invert.ts
Normal file
12
frontend/projects/shared/src/util/invert.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export function invert<
|
||||
T extends string | number | symbol,
|
||||
D extends string | number | symbol,
|
||||
>(obj: Record<T, D>): Record<D, T> {
|
||||
const result = {} as Record<D, T>
|
||||
|
||||
for (const key in obj) {
|
||||
result[obj[key]] = key
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import { TUI_DATE_FORMAT, TUI_DATE_SEPARATOR } from '@taiga-ui/cdk'
|
||||
import {
|
||||
tuiButtonOptionsProvider,
|
||||
tuiNumberFormatProvider,
|
||||
tuiTextfieldOptionsProvider,
|
||||
} from '@taiga-ui/core'
|
||||
import {
|
||||
TUI_DATE_TIME_VALUE_TRANSFORMER,
|
||||
@@ -33,6 +34,7 @@ export const APP_PROVIDERS: Provider[] = [
|
||||
IonNav,
|
||||
tuiNumberFormatProvider({ decimalSeparator: '.', thousandSeparator: '' }),
|
||||
tuiButtonOptionsProvider({ size: 'm' }),
|
||||
tuiTextfieldOptionsProvider({ hintOnDisabled: true }),
|
||||
{
|
||||
provide: TUI_DATE_FORMAT,
|
||||
useValue: 'MDY',
|
||||
|
||||
@@ -14,6 +14,13 @@ export abstract class Control<Spec extends ValueSpec, Value> {
|
||||
return this.control.spec
|
||||
}
|
||||
|
||||
// TODO: Properly handle already set immutable value
|
||||
get readOnly(): boolean {
|
||||
return (
|
||||
!!this.value && !!this.control.control?.pristine && this.control.immutable
|
||||
)
|
||||
}
|
||||
|
||||
get value(): Value | null {
|
||||
return this.control.value
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<div class="label">
|
||||
{{ spec.name }}
|
||||
<tui-tooltip
|
||||
*ngIf="spec.description"
|
||||
[content]="spec.description"
|
||||
*ngIf="spec.description || spec.disabled"
|
||||
[content]="spec | hint"
|
||||
></tui-tooltip>
|
||||
<button
|
||||
tuiLink
|
||||
|
||||
@@ -37,8 +37,9 @@ export class FormArrayComponent {
|
||||
|
||||
get canAdd(): boolean {
|
||||
return (
|
||||
!this.spec.maxLength ||
|
||||
this.spec.maxLength >= this.array.control.controls.length
|
||||
!this.spec.disabled &&
|
||||
(!this.spec.maxLength ||
|
||||
this.spec.maxLength >= this.array.control.controls.length)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
[maskito]="mask"
|
||||
[tuiTextfieldCustomContent]="color"
|
||||
[tuiTextfieldCleaner]="false"
|
||||
[tuiHintContent]="spec.description"
|
||||
[tuiHintContent]="spec | hint"
|
||||
[readOnly]="readOnly"
|
||||
[disabled]="!!spec.disabled"
|
||||
[pseudoInvalid]="invalid"
|
||||
[(ngModel)]="value"
|
||||
(focusedChange)="onFocus($event)"
|
||||
@@ -13,6 +15,7 @@
|
||||
<ng-template #color>
|
||||
<div class="wrapper" [style.color]="value">
|
||||
<input
|
||||
*ngIf="!readOnly && !spec.disabled"
|
||||
type="color"
|
||||
class="color"
|
||||
tabindex="-1"
|
||||
|
||||
@@ -10,8 +10,13 @@
|
||||
<form-toggle *ngSwitchCase="'toggle'"></form-toggle>
|
||||
</ng-container>
|
||||
<tui-error [error]="order | tuiFieldError | async"></tui-error>
|
||||
<ng-template *ngIf="spec.warning" #warning let-completeWith="completeWith">
|
||||
<ng-template
|
||||
*ngIf="spec.warning || immutable"
|
||||
#warning
|
||||
let-completeWith="completeWith"
|
||||
>
|
||||
{{ spec.warning }}
|
||||
<p *ngIf="immutable">This value cannot be changed once set!</p>
|
||||
<div class="buttons">
|
||||
<button
|
||||
tuiButton
|
||||
|
||||
@@ -39,6 +39,10 @@ export class FormControlComponent<
|
||||
readonly order = ERRORS
|
||||
private readonly alerts = inject(TuiAlertService)
|
||||
|
||||
get immutable(): boolean {
|
||||
return 'immutable' in this.spec && this.spec.immutable
|
||||
}
|
||||
|
||||
onFocus(focused: boolean) {
|
||||
this.focused = focused
|
||||
this.updateFocused(focused)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<ng-container [ngSwitch]="spec.inputmode" [tuiHintContent]="spec.description">
|
||||
<tui-input-time
|
||||
*ngSwitchCase="'time'"
|
||||
[tuiHintContent]="spec | hint"
|
||||
[disabled]="!!spec.disabled"
|
||||
[readOnly]="readOnly"
|
||||
[pseudoInvalid]="invalid"
|
||||
[ngModel]="getTime(value)"
|
||||
(ngModelChange)="value = $event?.toString() || null"
|
||||
@@ -11,6 +14,9 @@
|
||||
</tui-input-time>
|
||||
<tui-input-date
|
||||
*ngSwitchCase="'date'"
|
||||
[tuiHintContent]="spec | hint"
|
||||
[disabled]="!!spec.disabled"
|
||||
[readOnly]="readOnly"
|
||||
[pseudoInvalid]="invalid"
|
||||
[min]="spec.min ? (spec.min | tuiMapper : getLimit)[0] : min"
|
||||
[max]="spec.max ? (spec.max | tuiMapper : getLimit)[0] : max"
|
||||
@@ -22,6 +28,9 @@
|
||||
</tui-input-date>
|
||||
<tui-input-date-time
|
||||
*ngSwitchCase="'datetime-local'"
|
||||
[tuiHintContent]="spec | hint"
|
||||
[disabled]="!!spec.disabled"
|
||||
[readOnly]="readOnly"
|
||||
[pseudoInvalid]="invalid"
|
||||
[min]="spec.min ? (spec.min | tuiMapper : getLimit) : min"
|
||||
[max]="spec.max ? (spec.max | tuiMapper : getLimit) : max"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<tui-multi-select
|
||||
[tuiHintContent]="spec.description"
|
||||
[tuiHintContent]="spec | hint"
|
||||
[disabled]="disabled"
|
||||
[readOnly]="readOnly"
|
||||
[pseudoInvalid]="invalid"
|
||||
[editable]="false"
|
||||
[disabledItemHandler]="disabledItemHandler"
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Component } from '@angular/core'
|
||||
import { ValueSpecMultiselect } from '@start9labs/start-sdk/lib/config/configTypes'
|
||||
import { Control } from '../control'
|
||||
import { tuiPure } from '@taiga-ui/cdk'
|
||||
import { invert } from '@start9labs/shared'
|
||||
|
||||
@Component({
|
||||
selector: 'form-multiselect',
|
||||
@@ -11,13 +12,26 @@ export class FormMultiselectComponent extends Control<
|
||||
ValueSpecMultiselect,
|
||||
readonly string[]
|
||||
> {
|
||||
readonly items = Object.values(this.spec.values)
|
||||
private readonly inverted = invert(this.spec.values)
|
||||
|
||||
readonly disabledItemHandler = (item: string): boolean =>
|
||||
private readonly isDisabled = (item: string) =>
|
||||
Array.isArray(this.spec.disabled) &&
|
||||
this.spec.disabled.includes(this.inverted[item])
|
||||
|
||||
private readonly isExceedingLimit = (item: string) =>
|
||||
!!this.spec.maxLength &&
|
||||
this.selected.length >= this.spec.maxLength &&
|
||||
!this.selected.includes(item)
|
||||
|
||||
readonly disabledItemHandler = (item: string): boolean =>
|
||||
this.isDisabled(item) || this.isExceedingLimit(item)
|
||||
|
||||
readonly items = Object.values(this.spec.values)
|
||||
|
||||
get disabled(): boolean {
|
||||
return typeof this.spec.disabled === 'string'
|
||||
}
|
||||
|
||||
get selected(): string[] {
|
||||
return this.memoize(this.value)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<tui-input-number
|
||||
[tuiHintContent]="spec.description"
|
||||
[tuiHintContent]="spec | hint"
|
||||
[disabled]="!!spec.disabled"
|
||||
[readOnly]="readOnly"
|
||||
[tuiTextfieldPostfix]="spec.units || ''"
|
||||
[pseudoInvalid]="invalid"
|
||||
[precision]="Infinity"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<tui-select
|
||||
[tuiHintContent]="spec.description"
|
||||
[tuiHintContent]="spec | hint"
|
||||
[disabled]="disabled"
|
||||
[readOnly]="readOnly"
|
||||
[tuiTextfieldCleaner]="!spec.required"
|
||||
[pseudoInvalid]="invalid"
|
||||
[(ngModel)]="selected"
|
||||
@@ -9,7 +11,7 @@
|
||||
<span *ngIf="spec.required">*</span>
|
||||
<select
|
||||
tuiSelect
|
||||
[labels]="[spec.warning ? '⚠️ ' + spec.warning : '']"
|
||||
[items]="[items]"
|
||||
[items]="items"
|
||||
[disabledItemHandler]="disabledItemHandler"
|
||||
></select>
|
||||
</tui-select>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { ValueSpecSelect } from '@start9labs/start-sdk/lib/config/configTypes'
|
||||
import { invert } from '@start9labs/shared'
|
||||
import { Control } from '../control'
|
||||
|
||||
@Component({
|
||||
@@ -7,15 +8,23 @@ import { Control } from '../control'
|
||||
templateUrl: './form-select.component.html',
|
||||
})
|
||||
export class FormSelectComponent extends Control<ValueSpecSelect, string> {
|
||||
private readonly inverted = invert(this.spec.values)
|
||||
|
||||
readonly items = Object.values(this.spec.values)
|
||||
|
||||
readonly disabledItemHandler = (item: string) =>
|
||||
Array.isArray(this.spec.disabled) &&
|
||||
this.spec.disabled.includes(this.inverted[item])
|
||||
|
||||
get disabled(): boolean {
|
||||
return typeof this.spec.disabled === 'string'
|
||||
}
|
||||
|
||||
get selected(): string | null {
|
||||
return this.value && this.spec.values[this.value]
|
||||
}
|
||||
|
||||
set selected(value: string | null) {
|
||||
this.value =
|
||||
Object.entries(this.spec.values).find(([_, v]) => value === v)?.[0] ??
|
||||
null
|
||||
this.value = (value && this.inverted[value]) || null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<tui-input
|
||||
[tuiTextfieldCustomContent]="spec.masked ? toggle : ''"
|
||||
[tuiHintContent]="spec.description"
|
||||
[tuiHintContent]="spec | hint"
|
||||
[disabled]="!!spec.disabled"
|
||||
[readOnly]="readOnly"
|
||||
[pseudoInvalid]="invalid"
|
||||
[(ngModel)]="value"
|
||||
(focusedChange)="onFocus($event)"
|
||||
@@ -17,13 +19,24 @@
|
||||
/>
|
||||
</tui-input>
|
||||
<ng-template #toggle>
|
||||
<button
|
||||
*ngIf="spec.generate"
|
||||
tuiIconButton
|
||||
type="button"
|
||||
appearance="icon"
|
||||
title="Generate"
|
||||
size="xs"
|
||||
class="button"
|
||||
icon="tuiIconRefreshCcw"
|
||||
(click)="generate()"
|
||||
></button>
|
||||
<button
|
||||
tuiIconButton
|
||||
type="button"
|
||||
appearance="icon"
|
||||
title="Toggle masking"
|
||||
size="xs"
|
||||
class="toggle"
|
||||
class="button"
|
||||
[icon]="masked ? 'tuiIconEye' : 'tuiIconEyeOff'"
|
||||
(click)="masked = !masked"
|
||||
></button>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.toggle {
|
||||
.button {
|
||||
pointer-events: auto;
|
||||
margin-left: auto;
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
|
||||
.masked {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { ValueSpecText } from '@start9labs/start-sdk/lib/config/configTypes'
|
||||
import { Control } from '../control'
|
||||
import { getDefaultString } from '../../../util/config-utilities'
|
||||
|
||||
@Component({
|
||||
selector: 'form-text',
|
||||
@@ -9,4 +10,8 @@ import { Control } from '../control'
|
||||
})
|
||||
export class FormTextComponent extends Control<ValueSpecText, string> {
|
||||
masked = true
|
||||
|
||||
generate() {
|
||||
this.value = getDefaultString(this.spec.generate || '')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<tui-text-area
|
||||
[tuiHintContent]="spec.description"
|
||||
[tuiHintContent]="spec | hint"
|
||||
[disabled]="!!spec.disabled"
|
||||
[readOnly]="readOnly"
|
||||
[pseudoInvalid]="invalid"
|
||||
[expandable]="true"
|
||||
[rows]="6"
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
{{ spec.name }}
|
||||
<tui-tooltip
|
||||
*ngIf="spec.description"
|
||||
[content]="spec.description"
|
||||
*ngIf="spec.description || spec.disabled"
|
||||
[tuiHintContent]="spec | hint"
|
||||
></tui-tooltip>
|
||||
<tui-toggle
|
||||
size="l"
|
||||
[disabled]="!!spec.disabled || readOnly"
|
||||
[(ngModel)]="value"
|
||||
(focusedChange)="onFocus($event)"
|
||||
></tui-toggle>
|
||||
|
||||
@@ -48,6 +48,7 @@ import { MustachePipe } from './mustache.pipe'
|
||||
import { ControlDirective } from './control.directive'
|
||||
import { FormColorComponent } from './form-color/form-color.component'
|
||||
import { FormDatetimeComponent } from './form-datetime/form-datetime.component'
|
||||
import { HintPipe } from './hint.pipe'
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -98,6 +99,7 @@ import { FormDatetimeComponent } from './form-datetime/form-datetime.component'
|
||||
FormObjectComponent,
|
||||
FormArrayComponent,
|
||||
MustachePipe,
|
||||
HintPipe,
|
||||
ControlDirective,
|
||||
],
|
||||
exports: [FormGroupComponent],
|
||||
|
||||
21
frontend/projects/ui/src/app/components/form/hint.pipe.ts
Normal file
21
frontend/projects/ui/src/app/components/form/hint.pipe.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core'
|
||||
import { ValueSpec } from '@start9labs/start-sdk/lib/config/configTypes'
|
||||
|
||||
@Pipe({
|
||||
name: 'hint',
|
||||
})
|
||||
export class HintPipe implements PipeTransform {
|
||||
transform(spec: ValueSpec): string {
|
||||
const hint = []
|
||||
|
||||
if (spec.description) {
|
||||
hint.push(spec.description)
|
||||
}
|
||||
|
||||
if ('disabled' in spec && typeof spec.disabled === 'string') {
|
||||
hint.push(`Disabled: ${spec.disabled}`)
|
||||
}
|
||||
|
||||
return hint.join('\n\n')
|
||||
}
|
||||
}
|
||||
@@ -717,6 +717,18 @@ export module Mock {
|
||||
),
|
||||
}),
|
||||
),
|
||||
users: Value.multiselect({
|
||||
name: 'Users',
|
||||
default: [],
|
||||
maxLength: 2,
|
||||
disabled: ['matt'],
|
||||
values: {
|
||||
matt: 'Matt Hill',
|
||||
alex: 'Alex Inkin',
|
||||
blue: 'Blue J',
|
||||
lucy: 'Lucy',
|
||||
},
|
||||
}),
|
||||
advanced: Value.object(
|
||||
{
|
||||
name: 'Advanced',
|
||||
@@ -900,19 +912,19 @@ export module Mock {
|
||||
},
|
||||
),
|
||||
),
|
||||
'random-enum': Value.select({
|
||||
name: 'Random Enum',
|
||||
'random-select': Value.select({
|
||||
name: 'Random select',
|
||||
description: 'This is not even real.',
|
||||
warning: 'Be careful changing this!',
|
||||
required: {
|
||||
default: 'null',
|
||||
default: null,
|
||||
},
|
||||
values: {
|
||||
null: 'null',
|
||||
option1: 'option1',
|
||||
option2: 'option2',
|
||||
option3: 'option3',
|
||||
},
|
||||
disabled: ['option2'],
|
||||
}),
|
||||
'favorite-number':
|
||||
/* TODO: Convert range for this value ((-100,100])*/ Value.number({
|
||||
@@ -1037,8 +1049,13 @@ export module Mock {
|
||||
description: 'Options<ul><li>Item 1</li><li>Item 2</li></ul>',
|
||||
warning: 'Careful changing this',
|
||||
required: { default: 'internal' },
|
||||
disabled: ['fake'],
|
||||
},
|
||||
Variants.of({
|
||||
fake: {
|
||||
name: 'Fake',
|
||||
spec: Config.of({}),
|
||||
},
|
||||
internal: {
|
||||
name: 'Internal',
|
||||
spec: Config.of({}),
|
||||
@@ -1113,6 +1130,10 @@ export module Mock {
|
||||
}),
|
||||
'favorite-slogan': Value.text({
|
||||
name: 'Favorite Slogan',
|
||||
generate: {
|
||||
charset: 'a-z,A-Z,2-9',
|
||||
len: 20,
|
||||
},
|
||||
required: false,
|
||||
description:
|
||||
'You most favorite slogan in the whole world, used for paying you.',
|
||||
|
||||
@@ -52,8 +52,6 @@ export class FormService {
|
||||
): ValueSpecSelect {
|
||||
return {
|
||||
...spec,
|
||||
// TODO: implement disabled
|
||||
disabled: false,
|
||||
type: 'select',
|
||||
default: selection,
|
||||
values: Object.fromEntries(
|
||||
|
||||
Reference in New Issue
Block a user