terrier-engine 4.32.4 → 4.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -131,10 +131,9 @@ export default class DivePlotEditor extends ModalPart<DivePlotEditorState> {
131
131
  parent.div(".tt-flex.column.padded.large-gap", mainColumn => {
132
132
 
133
133
  mainColumn.div(".dd-plot-axes-and-preview.tt-flex.column.gap", axesAndPreview => {
134
- this.fields.compoundField(axesAndPreview, field => {
135
- field.label(".required").text("Title")
136
- this.fields.textInput(field, 'title', {class: 'shrink plot-title'})
137
- }).class('plot-title-field')
134
+ this.fields.compoundField(axesAndPreview, 'title', 'plot-title-field')
135
+ .label("Title", '.required')
136
+ .textInput({class: 'shrink plot-title'})
138
137
  axesAndPreview.div('.tt-flex.gap', row => {
139
138
  this.leftAxisFields.render(row)
140
139
  row.part(this.renderPart)
@@ -55,18 +55,17 @@ class TraceStyleFields extends TerrierFormFields<TraceStyle> {
55
55
 
56
56
  render(parent: PartTag) {
57
57
  parent.div('.tt-form.tt-flex.gap.wrap', container => {
58
- this.compoundField(container, field => {
59
- field.label().text("Color")
60
- this.textInput(field, 'stroke', {placeholder: 'Color'})
61
- })
62
- this.compoundField(container, field => {
63
- field.label().text("Width")
64
- this.numberInput(field, 'strokeWidth', {placeholder: 'Width'})
65
- })
66
- this.compoundField(container, field => {
67
- field.label().text("Dashes")
68
- this.textInput(field, 'strokeDasharray', {placeholder: 'Dashes'})
69
- })
58
+ this.compoundField(container, 'stroke')
59
+ .label("Color")
60
+ .textInput({placeholder: 'Color'})
61
+
62
+ this.compoundField(container, 'strokeWidth')
63
+ .label("Width")
64
+ .numberInput({placeholder: 'Width'})
65
+
66
+ this.compoundField(container, 'strokeDasharray')
67
+ .label("Dashes")
68
+ .textInput({placeholder: 'Dashes'})
70
69
  })
71
70
  }
72
71
 
@@ -165,10 +164,10 @@ export class DivePlotTraceEditor extends ModalPart<DivePlotTraceState> {
165
164
  renderContent(parent: PartTag): void {
166
165
  parent.div(".tt-form.tt-flex.large-gap.column.padded", mainColumn => {
167
166
  // query
168
- this.fields.compoundField(mainColumn, field => {
169
- field.label().text("Query")
170
- this.fields.select(field, 'query_id', this.queryOptions)
171
- })
167
+ this.fields
168
+ .compoundField(mainColumn, 'query_id')
169
+ .label("Query")
170
+ .select(this.queryOptions)
172
171
 
173
172
  // axes
174
173
  mainColumn.div('.tt-flex.gap', row => {
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "files": [
5
5
  "*"
6
6
  ],
7
- "version": "4.32.4",
7
+ "version": "4.34.0",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "https://github.com/Terrier-Tech/terrier-engine"
package/terrier/forms.ts CHANGED
@@ -1,13 +1,26 @@
1
- import {Field, FormFields, FormPartData, InputType, KeyOfType, SelectOptions} from "tuff-core/forms"
1
+ import {FieldConstructor, FormFields, FormPartData, InputType, KeyOfType, SelectOptions} from "tuff-core/forms"
2
2
  import {DbErrors} from "./db-client"
3
3
  import {PartTag} from "tuff-core/parts"
4
- import {DivTag, InputTag, InputTagAttrs, SelectTag, SelectTagAttrs, TextAreaTag, TextAreaTagAttrs} from "tuff-core/html"
4
+ import {
5
+ DefaultTagAttrs,
6
+ DivTag,
7
+ InputTag,
8
+ InputTagAttrs,
9
+ SelectTag,
10
+ SelectTagAttrs,
11
+ TextAreaTag,
12
+ TextAreaTagAttrs
13
+ } from "tuff-core/html"
5
14
  import TerrierPart from "./parts/terrier-part"
6
15
  import GlypPicker from "./parts/glyp-picker"
7
16
  import Glyps from "./glyps"
8
17
  import Messages from "tuff-core/messages"
9
18
  import {Logger} from "tuff-core/logging"
10
19
  import Strings from "tuff-core/strings"
20
+ import Theme, {Action, ColorName, IconName} from "./theme"
21
+ import Objects from "tuff-core/objects"
22
+ import {InlineStyle} from "tuff-core/tags"
23
+ import Hints, {Hint, HintRenderOptions} from "./hints"
11
24
 
12
25
  const log = new Logger("TerrierForms")
13
26
 
@@ -86,33 +99,27 @@ export class TerrierFormFields<T extends FormPartData> extends FormFields<T> {
86
99
  })
87
100
  }
88
101
 
89
- protected input<Key extends KeyOfType<T, any> & string>(parent: PartTag, type: InputType, name: Key, serializerType: {
90
- new(name: string): Field<any, Element>
91
- }, attrs?: InputTagAttrs): InputTag {
102
+ protected addErrorClass(name: string, attrs: DefaultTagAttrs) {
92
103
  if (this.errors && this.errors[name]) {
93
104
  attrs ||= {}
94
105
  attrs.classes ||= []
95
106
  attrs.classes.push('error')
96
107
  }
108
+ }
109
+
110
+ input<Key extends KeyOfType<T,any> & string>(parent: PartTag, type: InputType, name: Key, serializerType: FieldConstructor<any, Element>, attrs: InputTagAttrs={}): InputTag {
111
+ this.addErrorClass(name, attrs)
97
112
  return super.input(parent, type, name, serializerType, attrs);
98
113
  }
99
114
 
100
- select<Key extends KeyOfType<T, string> & string>(parent: PartTag, name: Key, options?: SelectOptions, attrs: SelectTagAttrs = {}): SelectTag {
101
- if (this.errors && this.errors[name]) {
102
- attrs ||= {}
103
- attrs.classes ||= []
104
- attrs.classes.push('error')
105
- }
106
- return super.select(parent, name, options, attrs);
115
+ select<Key extends keyof T & string, TSerializer extends FieldConstructor<T[Key], HTMLSelectElement>>(parent: PartTag, name: Key, options?: SelectOptions, attrs: SelectTagAttrs = {}, serializerType?: TSerializer): SelectTag {
116
+ this.addErrorClass(name, attrs)
117
+ return super.select(parent, name, options, attrs, serializerType);
107
118
  }
108
119
 
109
- textArea<Key extends KeyOfType<T, string> & string>(parent: PartTag, name: Key, attrs: TextAreaTagAttrs={}): TextAreaTag {
110
- if (this.errors && this.errors[name]) {
111
- attrs ||= {}
112
- attrs.classes ||= []
113
- attrs.classes.push('error')
114
- }
115
- return super.textArea(parent, name, attrs);
120
+ textArea<Key extends keyof T & string, TSerializer extends FieldConstructor<T[Key], HTMLTextAreaElement>>(parent: PartTag, name: Key, attrs: SelectTagAttrs = {}, serializerType?: TSerializer): TextAreaTag {
121
+ this.addErrorClass(name, attrs)
122
+ return super.textArea(parent, name, attrs, serializerType);
116
123
  }
117
124
 
118
125
  /**
@@ -142,17 +149,12 @@ export class TerrierFormFields<T extends FormPartData> extends FormFields<T> {
142
149
  }).emitClick(this.pickGlypKey, {key})
143
150
  }
144
151
 
152
+ compoundField<Key extends keyof T & string>(parent: PartTag, key: Key, ...classes: string[]): CompoundFieldBuilder<T, Key> {
153
+ return new CompoundFieldBuilder(this, parent, key, (this.part as TerrierPart<any>).theme, ...classes)
154
+ }
145
155
 
146
- /**
147
- * Creates a .tt-compound-field tag and passes the field to the nested function.
148
- * I suppose it's just a slight convenience over having `parent.div(".tt-compound-field"...` everywhere.
149
- * @param parent
150
- * @param fun a function that accepts the field as an argument, to actually populate the field
151
- */
152
- compoundField(parent: PartTag, fun: (field: DivTag) => any) {
153
- return parent.div(".tt-compound-field", field => {
154
- fun(field)
155
- })
156
+ fileCompoundField<Key extends KeyOfType<T, File> & string>(parent: PartTag, key: Key, ...classes: string[]): FileCompoundFieldBuilder<T, Key> {
157
+ return new FileCompoundFieldBuilder(this, parent, key, (this.part as TerrierPart<any>).theme, ...classes)
156
158
  }
157
159
 
158
160
  /**
@@ -163,7 +165,7 @@ export class TerrierFormFields<T extends FormPartData> extends FormFields<T> {
163
165
  * @param title the title to put in the label span
164
166
  * @param attrs attributes for the radio input
165
167
  */
166
- radioLabel<Key extends KeyOfType<T, string> & string>(parent: PartTag, name: Key, value: string, title?: string, attrs: InputTagAttrs = {}) {
168
+ radioLabel<Key extends KeyOfType<T, string> & string>(parent: PartTag, name: Key, value: T[Key], title?: string, attrs: InputTagAttrs = {}) {
167
169
  return parent.label('.body-size', label => {
168
170
  this.radio(label, name, value, attrs)
169
171
  label.span().text(title || value)
@@ -172,6 +174,147 @@ export class TerrierFormFields<T extends FormPartData> extends FormFields<T> {
172
174
 
173
175
  }
174
176
 
177
+ class CompoundFieldBuilder<T extends Record<string, unknown>, K extends keyof T & string> {
178
+
179
+ field!: DivTag
180
+
181
+ constructor(readonly formFields: TerrierFormFields<T>, readonly parent: PartTag, readonly key: K, readonly theme: Theme, ...classes: string[]) {
182
+ this.field = parent.div('.tt-compound-field')
183
+ // add the error class if this key (or key_id) is in the errors object
184
+ if (formFields.errors && (formFields.errors[key] || formFields.errors[`${key}_id`] || formFields.errors[key.replace(/_id$/,'')])) {
185
+ this.field.class('error')
186
+ }
187
+ if (classes.length) {
188
+ this.field.class(...classes)
189
+ }
190
+ }
191
+
192
+ hiddenInput(attrs?: InputTagAttrs, serializerType?: FieldConstructor<T[K], HTMLInputElement>): this {
193
+ this.formFields.hiddenInput(this.field, this.key, attrs ?? {}, serializerType)
194
+ return this
195
+ }
196
+ textInput(attrs?: InputTagAttrs, serializerType?: FieldConstructor<T[K], HTMLInputElement>): this {
197
+ this.formFields.textInput(this.field, this.key, attrs ?? {}, serializerType)
198
+ return this
199
+ }
200
+ numberInput(attrs?: InputTagAttrs, serializerType?: FieldConstructor<T[K], HTMLInputElement>): this {
201
+ this.formFields.numberInput(this.field, this.key, attrs ?? {}, serializerType)
202
+ return this
203
+ }
204
+ emailInput(attrs?: InputTagAttrs, serializerType?: FieldConstructor<T[K], HTMLInputElement>): this {
205
+ this.formFields.emailInput(this.field, this.key, attrs ?? {}, serializerType)
206
+ return this
207
+ }
208
+ phoneInput(attrs?: InputTagAttrs, serializerType?: FieldConstructor<T[K], HTMLInputElement>): this {
209
+ this.formFields.phoneInput(this.field, this.key, attrs ?? {}, serializerType)
210
+ return this
211
+ }
212
+ passwordInput(attrs?: InputTagAttrs, serializerType?: FieldConstructor<T[K], HTMLInputElement>): this {
213
+ this.formFields.passwordInput(this.field, this.key, attrs ?? {}, serializerType)
214
+ return this
215
+ }
216
+ searchInput(attrs?: InputTagAttrs, serializerType?: FieldConstructor<T[K], HTMLInputElement>): this {
217
+ this.formFields.searchInput(this.field, this.key, attrs ?? {}, serializerType)
218
+ return this
219
+ }
220
+ urlInput(attrs?: InputTagAttrs, serializerType?: FieldConstructor<T[K], HTMLInputElement>): this {
221
+ this.formFields.urlInput(this.field, this.key, attrs ?? {}, serializerType)
222
+ return this
223
+ }
224
+ textArea(attrs?: TextAreaTagAttrs, serializerType?: FieldConstructor<T[K], HTMLTextAreaElement>): this {
225
+ this.formFields.textArea(this.field, this.key, attrs ?? {}, serializerType)
226
+ return this
227
+ }
228
+ dateInput(attrs?: InputTagAttrs, serializerType?: FieldConstructor<T[K], HTMLInputElement>): this {
229
+ this.formFields.dateInput(this.field, this.key, attrs ?? {}, serializerType)
230
+ return this
231
+ }
232
+ timeInput(attrs?: InputTagAttrs, serializerType?: FieldConstructor<T[K], HTMLInputElement>): this {
233
+ this.formFields.timeInput(this.field, this.key, attrs ?? {}, serializerType)
234
+ return this
235
+ }
236
+ dateTimeInput(attrs?: InputTagAttrs, serializerType?: FieldConstructor<T[K], HTMLInputElement>): this {
237
+ this.formFields.dateTimeInput(this.field, this.key, attrs ?? {}, serializerType)
238
+ return this
239
+ }
240
+ monthInput(attrs?: InputTagAttrs, serializerType?: FieldConstructor<T[K], HTMLInputElement>): this {
241
+ this.formFields.monthInput(this.field, this.key, attrs ?? {}, serializerType)
242
+ return this
243
+ }
244
+ weekInput(attrs?: InputTagAttrs, serializerType?: FieldConstructor<T[K], HTMLInputElement>): this {
245
+ this.formFields.weekInput(this.field, this.key, attrs ?? {}, serializerType)
246
+ return this
247
+ }
248
+ radio(value: T[K], attrs?: InputTagAttrs, serializerType?: FieldConstructor<T[K], HTMLInputElement>): this {
249
+ this.formFields.radio(this.field, this.key, value, attrs ?? {}, serializerType)
250
+ return this
251
+ }
252
+ checkbox(attrs?: InputTagAttrs, serializerType?: FieldConstructor<T[K], HTMLInputElement>): this {
253
+ this.formFields.checkbox(this.field, this.key, attrs ?? {}, serializerType)
254
+ return this
255
+ }
256
+ select(selectOptions?: SelectOptions, attrs?: InputTagAttrs, serializerType?: FieldConstructor<T[K], HTMLSelectElement>): this {
257
+ this.formFields.select(this.field, this.key, selectOptions, attrs ?? {}, serializerType)
258
+ return this
259
+ }
260
+ colorInput(attrs?: InputTagAttrs, serializerType?: FieldConstructor<T[K], HTMLInputElement>): this {
261
+ this.formFields.colorInput(this.field, this.key, attrs ?? {}, serializerType)
262
+ return this
263
+ }
264
+
265
+ readonly(text?: string): this {
266
+ this.field.div('.readonly-field', {text: text ?? Objects.safeToString(this.formFields.data[this.key]) })
267
+ return this
268
+ }
269
+
270
+ label(text: string, ...classes: string[]): this {
271
+ this.field.label({ text, htmlFor: `${this.formFields.part.id}-${this.key}`, classes })
272
+ return this
273
+ }
274
+
275
+ tooltip(text: string): this {
276
+ this.field.dataAttr('tooltip', text)
277
+ return this
278
+ }
279
+
280
+ hint(hint: Hint, options?: HintRenderOptions): this {
281
+ Hints.renderHint(this.theme, this.field, hint, options)
282
+ return this
283
+ }
284
+
285
+ icon(icon: IconName, color: ColorName | null = 'secondary'): this {
286
+ this.field.label('.icon-only', label => {
287
+ this.theme.renderIcon(label, icon, color)
288
+ })
289
+ return this
290
+ }
291
+
292
+ css(styles: InlineStyle): this {
293
+ this.field.css(styles)
294
+ return this
295
+ }
296
+
297
+ action(action: Action, color: ColorName = 'link'): this {
298
+ action.classes ||= []
299
+ action.classes.push(color)
300
+ this.theme.renderActions(this.field, action, {iconColor: color})
301
+ return this
302
+ }
303
+
304
+ class(...s: string[]): this {
305
+ this.field.class(...s)
306
+ return this
307
+ }
308
+ }
309
+
310
+ class FileCompoundFieldBuilder<T extends Record<string, unknown>, K extends KeyOfType<T, File> & string> extends CompoundFieldBuilder<T, K> {
311
+ fileInput(attrs?: InputTagAttrs): this {
312
+ this.formFields.fileInput(this.field, this.key, attrs ?? {})
313
+ return this
314
+ }
315
+ }
316
+
317
+
175
318
 
176
319
 
177
320
  ////////////////////////////////////////////////////////////////////////////////