terrier-engine 4.5.3 → 4.7.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.
@@ -1,7 +1,7 @@
1
1
  import {DdUser} from "./dd-user"
2
2
  import Api from "../terrier/api"
3
3
  import {DdDiveGroup} from "./gen/models"
4
- import {arrays} from "tuff-core";
4
+ import Arrays from "tuff-core/arrays"
5
5
  import {SelectOptions} from "tuff-core/forms"
6
6
  import {Logger} from "tuff-core/logging"
7
7
 
@@ -76,7 +76,7 @@ export default class DdSession {
76
76
  }
77
77
 
78
78
  groupsInOrder(): DdDiveGroup[] {
79
- return arrays.sortBy(Object.values(this.data.groupMap), 'name')
79
+ return Arrays.sortBy(Object.values(this.data.groupMap), 'name')
80
80
  }
81
81
 
82
82
  groupOptions(): SelectOptions {
@@ -5,7 +5,6 @@ import {Query, QueryModelPicker} from "../queries/queries"
5
5
  import QueryEditor from "../queries/query-editor"
6
6
  import {Logger} from "tuff-core/logging"
7
7
  import QueryForm from "../queries/query-form"
8
- import {arrays, messages} from "tuff-core"
9
8
  import {TabContainerPart} from "../../terrier/tabs"
10
9
  import PagePart from "../../terrier/parts/page-part"
11
10
  import ContentPart from "../../terrier/parts/content-part"
@@ -15,7 +14,9 @@ import Ids from "../../terrier/ids"
15
14
  import Db from "../dd-db"
16
15
  import DdSession from "../dd-session"
17
16
  import {DiveRunModal} from "./dive-runs"
18
- import Nav from "tuff-core/nav";
17
+ import Nav from "tuff-core/nav"
18
+ import Messages from "tuff-core/messages"
19
+ import Arrays from "tuff-core/arrays"
19
20
 
20
21
  const log = new Logger("DiveEditor")
21
22
 
@@ -34,9 +35,9 @@ export default class DiveEditor extends ContentPart<DiveEditorState> {
34
35
 
35
36
  tabs!: TabContainerPart
36
37
 
37
- newQueryKey = messages.untypedKey()
38
+ newQueryKey = Messages.untypedKey()
38
39
 
39
- static readonly diveChangedKey = messages.untypedKey()
40
+ static readonly diveChangedKey = Messages.untypedKey()
40
41
 
41
42
  queries = new Array<Query>()
42
43
 
@@ -88,7 +89,7 @@ export default class DiveEditor extends ContentPart<DiveEditorState> {
88
89
 
89
90
  deleteQuery(id: string) {
90
91
  log.info(`Deleting query ${id}`)
91
- if (arrays.deleteIf(this.queries, q => q.id == id) > 0) {
92
+ if (Arrays.deleteIf(this.queries, q => q.id == id) > 0) {
92
93
  this.tabs.removeTab(id)
93
94
  this.dirty()
94
95
  }
@@ -102,7 +103,7 @@ export default class DiveEditor extends ContentPart<DiveEditorState> {
102
103
  parent.part(this.tabs)
103
104
  }
104
105
 
105
- static readonly deleteQueryKey = messages.typedKey<{ id: string }>()
106
+ static readonly deleteQueryKey = Messages.typedKey<{ id: string }>()
106
107
 
107
108
  async serialize(): Promise<DdDive> {
108
109
  const queries = this.queries
@@ -117,10 +118,10 @@ export class DiveEditorPage extends PagePart<{id: string}> {
117
118
  editor!: DiveEditor
118
119
  session!: DdSession
119
120
 
120
- showHintsKey = messages.untypedKey()
121
- saveKey = messages.untypedKey()
122
- discardKey = messages.untypedKey()
123
- runKey = messages.untypedKey()
121
+ showHintsKey = Messages.untypedKey()
122
+ saveKey = Messages.untypedKey()
123
+ discardKey = Messages.untypedKey()
124
+ runKey = Messages.untypedKey()
124
125
 
125
126
  async init() {
126
127
  log.info(`Loading dive ${this.state.id}`)
@@ -233,7 +234,7 @@ type NewQueryState = {
233
234
 
234
235
  class NewQueryModal extends ModalPart<NewQueryState> {
235
236
 
236
- addKey = messages.untypedKey()
237
+ addKey = Messages.untypedKey()
237
238
  settingsForm!: QueryForm
238
239
  modelPicker!: QueryModelPicker
239
240
 
@@ -2,7 +2,6 @@ import {Logger} from "tuff-core/logging"
2
2
  import PagePart from "../../terrier/parts/page-part"
3
3
  import {PartTag} from "tuff-core/parts"
4
4
  import Schema, {SchemaDef} from "../../terrier/schema"
5
- import {arrays, messages} from "tuff-core"
6
5
  import {DdDive, DdDiveGroup, UnpersistedDdDive, UnpersistedDdDiveGroup} from "../gen/models"
7
6
  import Dives, {DiveListResult} from "./dives"
8
7
  import {GroupEditorModal} from "./group-editor"
@@ -11,6 +10,8 @@ import {IconName} from "../../terrier/theme"
11
10
  import {routes} from "../dd-routes"
12
11
  import {DiveSettingsModal} from "./dive-settings"
13
12
  import DdSession from "../dd-session"
13
+ import Messages from "tuff-core/messages"
14
+ import Arrays from "tuff-core/arrays"
14
15
 
15
16
  const log = new Logger("DiveList")
16
17
 
@@ -21,10 +22,10 @@ const log = new Logger("DiveList")
21
22
 
22
23
  export class DiveListPage extends PagePart<{}> {
23
24
 
24
- newGroupKey = messages.untypedKey()
25
- editGroupKey = messages.typedKey<{id: string}>()
26
- newDiveKey = messages.typedKey<{group_id: string}>()
27
- editDiveKey = messages.typedKey<{id: string}>()
25
+ newGroupKey = Messages.untypedKey()
26
+ editGroupKey = Messages.typedKey<{id: string}>()
27
+ newDiveKey = Messages.typedKey<{group_id: string}>()
28
+ editDiveKey = Messages.typedKey<{id: string}>()
28
29
 
29
30
  session!: DdSession
30
31
  result!: DiveListResult
@@ -104,16 +105,16 @@ export class DiveListPage extends PagePart<{}> {
104
105
  this.result = await Dives.list()
105
106
  log.info("Loading data dive list", this.result)
106
107
  this.groupMap = this.session.data.groupMap
107
- this.diveMap = arrays.indexBy(this.result.dives, 'id')
108
+ this.diveMap = Arrays.indexBy(this.result.dives, 'id')
108
109
  this.dirty()
109
110
  }
110
111
 
111
112
  renderContent(parent: PartTag): void {
112
113
 
113
- const groupedDives = arrays.groupBy(this.result.dives, 'dd_dive_group_id')
114
+ const groupedDives = Arrays.groupBy(this.result.dives, 'dd_dive_group_id')
114
115
 
115
116
  parent.div('.dd-group-grid', grid => {
116
- const groups = arrays.sortBy(Object.values(this.groupMap), 'name')
117
+ const groups = Arrays.sortBy(Object.values(this.groupMap), 'name')
117
118
  for (const group of groups) {
118
119
  this.renderGroupPanel(grid, group, groupedDives[group.id] || [])
119
120
  }
@@ -127,7 +128,7 @@ export class DiveListPage extends PagePart<{}> {
127
128
  .classes('group')
128
129
  .content(content => {
129
130
  content.class('tt-list')
130
- for (const dive of arrays.sortBy(dives, 'name')) {
131
+ for (const dive of Arrays.sortBy(dives, 'name')) {
131
132
  this.renderDiveRow(content, dive)
132
133
  }
133
134
  })
@@ -5,7 +5,6 @@ import Db from "../dd-db"
5
5
  import Api, {ErrorEvent} from "../../terrier/api"
6
6
  import {Query} from "../queries/queries"
7
7
  import {DivTag, HtmlParentTag} from "tuff-core/html"
8
- import {messages} from "tuff-core"
9
8
  import {IconName} from "../../terrier/theme"
10
9
  import Filters, {DateRangeFilter, DirectFilter, FilterInput, InclusionFilter} from "../queries/filters"
11
10
  import Dives from "./dives"
@@ -16,6 +15,8 @@ import inflection from "inflection"
16
15
  import Dates, {DateLiteral, DatePeriodPickerPart, DatePeriodPickerState, LiteralDateRange} from "../queries/dates"
17
16
  import dayjs from "dayjs"
18
17
  import {ProgressBarPart} from "../../terrier/progress";
18
+ import {LogListPart} from "../../terrier/logging";
19
+ import Messages from "tuff-core/messages"
19
20
 
20
21
  const log = new Logger("DiveRuns")
21
22
 
@@ -26,6 +27,10 @@ type RunQueryResult = {
26
27
  message?: string
27
28
  }
28
29
 
30
+ type InitRunResult = {
31
+ total_steps: number
32
+ }
33
+
29
34
  const statusIcons: Record<RunQueryResult['status'], IconName> = {
30
35
  pending: 'glyp-pending',
31
36
  success: 'glyp-complete',
@@ -48,9 +53,10 @@ export class DiveRunModal extends ModalPart<{dive: DdDive }> {
48
53
  fileOutput?: RunFileOutput
49
54
  queryResults: Record<string, RunQueryResult> = {}
50
55
  progressBar!: ProgressBarPart
56
+ logList!: LogListPart
51
57
 
52
- startKey = messages.untypedKey()
53
- pickDateKey = messages.typedKey<{ input_key: string }>()
58
+ startKey = Messages.untypedKey()
59
+ pickDateKey = Messages.typedKey<{ input_key: string }>()
54
60
 
55
61
  async init() {
56
62
  this.setTitle("Run Dive")
@@ -58,9 +64,8 @@ export class DiveRunModal extends ModalPart<{dive: DdDive }> {
58
64
 
59
65
  this.schema = await Schema.get()
60
66
 
61
- // progress is # queries +1 for creating and run and +1 for the output
62
- const total = (this.state.dive.query_data?.queries?.length || 0) + 2
63
- this.progressBar = this.makePart(ProgressBarPart, {total})
67
+ this.progressBar = this.makePart(ProgressBarPart, {total: 10})
68
+ this.logList = this.makePart(LogListPart, {})
64
69
 
65
70
  // initialize the inputs
66
71
  this.filters = Dives.computeFilterInputs(this.schema, this.state.dive)
@@ -123,6 +128,7 @@ export class DiveRunModal extends ModalPart<{dive: DdDive }> {
123
128
  const res = await Db().upsert('dd_dive_run', newRun)
124
129
  if (res.status == 'success' && res.record) {
125
130
  this.beginStreaming(res.record)
131
+ this.logList.info(`Created dive run`)
126
132
  } else {
127
133
  this.alertToast(`Error creating dive run: ${res.message}`)
128
134
  }
@@ -133,6 +139,11 @@ export class DiveRunModal extends ModalPart<{dive: DdDive }> {
133
139
  beginStreaming(run: DdDiveRun) {
134
140
  this.run = run
135
141
  Api.stream(`/data_dive/stream_run/${this.run.id}`)
142
+ .on<InitRunResult>('init_run', res => {
143
+ this.progressBar.setTotal(res.total_steps)
144
+ log.info(`Total steps for run: ${res.total_steps}`)
145
+ this.dirty()
146
+ })
136
147
  .on<RunQueryResult>('query_result', res => {
137
148
  this.queryResults[res.id] = res
138
149
  this.progressBar.increment()
@@ -144,6 +155,12 @@ export class DiveRunModal extends ModalPart<{dive: DdDive }> {
144
155
  this.progressBar.complete('success')
145
156
  this.dirty()
146
157
  })
158
+ .onLog(evt => {
159
+ this.progressBar.increment()
160
+ log.log(evt.level, evt.message)
161
+ this.logList.push(evt)
162
+ this.dirty()
163
+ })
147
164
  .onError(evt => {
148
165
  this.error = evt
149
166
  this.stopActionLoading()
@@ -160,6 +177,8 @@ export class DiveRunModal extends ModalPart<{dive: DdDive }> {
160
177
  renderContent(parent: PartTag): void {
161
178
  parent.div('.tt-flex.padded.gap.column', col => {
162
179
  col.part(this.progressBar)
180
+
181
+ // inputs and outputs row
163
182
  col.div('.tt-flex.collapsible.gap.tt-form', row => {
164
183
  // inputs
165
184
  row.div('.tt-flex.column.shrink.dd-dive-run-inputs', col => {
@@ -197,6 +216,9 @@ export class DiveRunModal extends ModalPart<{dive: DdDive }> {
197
216
  }
198
217
  })
199
218
  })
219
+
220
+ // log
221
+ col.part(this.logList)
200
222
  })
201
223
  }
202
224
 
@@ -1,6 +1,5 @@
1
1
  import TerrierPart from "../../terrier/parts/terrier-part"
2
2
  import {Logger} from "tuff-core/logging"
3
- import {messages} from "tuff-core"
4
3
  import {PartTag} from "tuff-core/parts"
5
4
  import {DdDive, DdDiveEnumFields, UnpersistedDdDive} from "../gen/models"
6
5
  import inflection from "inflection"
@@ -15,6 +14,7 @@ import {DbErrors} from "../../terrier/db-client"
15
14
  import {TerrierFormFields} from "../../terrier/forms"
16
15
  import DdSession from "../dd-session"
17
16
  import Dives from "./dives";
17
+ import Messages from "tuff-core/messages"
18
18
 
19
19
  const log = new Logger("DiveForm")
20
20
 
@@ -39,7 +39,7 @@ class DiveForm extends TerrierPart<{dive: DiveSettings, session: DdSession}> {
39
39
  }, {attach: 'passive'})
40
40
  }
41
41
 
42
- static readonly settingsChangedKey = messages.typedKey<DiveSettings>()
42
+ static readonly settingsChangedKey = Messages.typedKey<DiveSettings>()
43
43
 
44
44
 
45
45
  get parentClasses(): Array<string> {
@@ -109,8 +109,8 @@ export class DiveSettingsModal extends ModalPart<DiveSettingsState> {
109
109
 
110
110
  settingsForm!: DiveForm
111
111
  modelPicker!: QueryModelPicker
112
- saveKey = messages.untypedKey()
113
- deleteKey = messages.untypedKey()
112
+ saveKey = Messages.untypedKey()
113
+ deleteKey = Messages.untypedKey()
114
114
  isNew = true
115
115
 
116
116
  async init() {
@@ -3,12 +3,12 @@ import TerrierPart from "../../terrier/parts/terrier-part"
3
3
  import {DdDiveGroup, UnpersistedDdDiveGroup} from "../gen/models"
4
4
  import Db from "../dd-db"
5
5
  import {PartTag} from "tuff-core/parts"
6
- import {messages} from "tuff-core"
7
6
  import {DbErrors} from "../../terrier/db-client"
8
7
  import DdSession from "../dd-session"
9
8
  import Nav from "tuff-core/nav"
10
9
  import {routes} from "../dd-routes"
11
10
  import {TerrierFormFields} from "../../terrier/forms"
11
+ import Messages from "tuff-core/messages"
12
12
 
13
13
  class GroupForm extends TerrierPart<{ group: UnpersistedDdDiveGroup }> {
14
14
 
@@ -66,8 +66,8 @@ export type GroupModalState = {
66
66
  export class GroupEditorModal extends ModalPart<GroupModalState> {
67
67
 
68
68
  form!: GroupForm
69
- saveKey = messages.untypedKey()
70
- deleteKey = messages.untypedKey()
69
+ saveKey = Messages.untypedKey()
70
+ deleteKey = Messages.untypedKey()
71
71
 
72
72
  async init() {
73
73
  const group = this.state.group
@@ -4,12 +4,13 @@ import {TableRef, TableView} from "./tables"
4
4
  import {Logger} from "tuff-core/logging"
5
5
  import {FormFields, SelectOptions} from "tuff-core/forms"
6
6
  import Forms from "../../terrier/forms"
7
- import {arrays, messages} from "tuff-core"
8
7
  import Objects from "tuff-core/objects"
9
8
  import {ModalPart} from "../../terrier/modals";
10
9
  import TerrierFormPart from "../../terrier/parts/terrier-form-part"
11
10
  import {Dropdown} from "../../terrier/dropdowns"
12
11
  import DiveEditor from "../dives/dive-editor"
12
+ import Messages from "tuff-core/messages"
13
+ import Arrays from "tuff-core/arrays"
13
14
 
14
15
  const log = new Logger("Columns")
15
16
 
@@ -90,10 +91,10 @@ export type ColumnsEditorState = {
90
91
  tableView: TableView<TableRef>
91
92
  }
92
93
 
93
- const saveKey = messages.untypedKey()
94
- const addKey = messages.untypedKey()
95
- const addSingleKey = messages.typedKey<{ name: string }>()
96
- const removeKey = messages.typedKey<{id: string}>()
94
+ const saveKey = Messages.untypedKey()
95
+ const addKey = Messages.untypedKey()
96
+ const addSingleKey = Messages.typedKey<{ name: string }>()
97
+ const removeKey = Messages.typedKey<{id: string}>()
97
98
 
98
99
  /**
99
100
  * A modal that lets the user edit the columns being referenced for a particular table.
@@ -235,9 +236,9 @@ export class ColumnsEditorModal extends ModalPart<ColumnsEditorState> {
235
236
  }
236
237
 
237
238
  removeColumn(id: string) {
238
- const col = arrays.find(this.columnStates, c => c.id == id)
239
+ const col = Arrays.find(this.columnStates, c => c.id == id)
239
240
  if (col) {
240
- this.columnStates = arrays.without(this.columnStates, col)
241
+ this.columnStates = Arrays.without(this.columnStates, col)
241
242
  this.updateColumnEditors()
242
243
  }
243
244
  }
@@ -315,9 +316,9 @@ class ColumnEditor extends TerrierFormPart<ColumnState> {
315
316
  // Add Column Dropdown
316
317
  ////////////////////////////////////////////////////////////////////////////////
317
318
 
318
- const checkAllKey = messages.untypedKey()
319
- const applySelectionKey = messages.untypedKey()
320
- const checkChangedKey = messages.typedKey<{column: string}>()
319
+ const checkAllKey = Messages.untypedKey()
320
+ const applySelectionKey = Messages.untypedKey()
321
+ const checkChangedKey = Messages.typedKey<{column: string}>()
321
322
 
322
323
  type SelectColumnsCallback = (columns: string[]) => any
323
324
 
@@ -2,8 +2,8 @@ import inflection from "inflection"
2
2
  import dayjs from "dayjs"
3
3
  import {Dropdown} from "../../terrier/dropdowns"
4
4
  import {PartTag} from "tuff-core/parts"
5
- import {messages} from "tuff-core"
6
5
  import {Logger} from "tuff-core/logging"
6
+ import Messages from "tuff-core/messages"
7
7
 
8
8
  const log = new Logger("Dates")
9
9
 
@@ -211,7 +211,7 @@ export type DatePeriodPickerState = {
211
211
  */
212
212
  export class DatePeriodPickerPart extends Dropdown<DatePeriodPickerState> {
213
213
 
214
- periodKey = messages.typedKey<{period: string}>()
214
+ periodKey = Messages.typedKey<{period: string}>()
215
215
 
216
216
  async init() {
217
217
  await super.init()
@@ -2,16 +2,17 @@ import {Part, PartTag} from "tuff-core/parts"
2
2
  import Dates, {DateLiteral, VirtualDatePeriod, VirtualDateRange} from "./dates"
3
3
  import {ColumnDef, ModelDef, SchemaDef} from "../../terrier/schema"
4
4
  import {TableRef, TableView} from "./tables"
5
- import {arrays, messages} from "tuff-core"
6
5
  import {Logger} from "tuff-core/logging"
7
6
  import Objects from "tuff-core/objects"
8
7
  import inflection from "inflection"
9
- import {ModalPart} from "../../terrier/modals";
8
+ import {ModalPart} from "../../terrier/modals"
10
9
  import TerrierFormPart from "../../terrier/parts/terrier-form-part"
11
10
  import {Dropdown} from "../../terrier/dropdowns"
12
11
  import dayjs from "dayjs"
13
12
  import Format from "../../terrier/format"
14
13
  import DiveEditor from "../dives/dive-editor"
14
+ import Messages from "tuff-core/messages"
15
+ import Arrays from "tuff-core/arrays"
15
16
 
16
17
  const log = new Logger("Filters")
17
18
 
@@ -125,9 +126,9 @@ export type FiltersEditorState = {
125
126
  tableView: TableView<TableRef>
126
127
  }
127
128
 
128
- const saveKey = messages.untypedKey()
129
- const addKey = messages.untypedKey()
130
- const removeKey = messages.typedKey<{ id: string }>()
129
+ const saveKey = Messages.untypedKey()
130
+ const addKey = Messages.untypedKey()
131
+ const removeKey = Messages.typedKey<{ id: string }>()
131
132
 
132
133
  export class FiltersEditorModal extends ModalPart<FiltersEditorState> {
133
134
 
@@ -202,10 +203,10 @@ export class FiltersEditorModal extends ModalPart<FiltersEditorState> {
202
203
  }
203
204
 
204
205
  removeFilter(id: string) {
205
- const filter = arrays.find(this.filterStates, f => f.id == id)
206
+ const filter = Arrays.find(this.filterStates, f => f.id == id)
206
207
  if (filter) {
207
208
  log.info(`Removing filter ${id}`, filter)
208
- this.filterStates = arrays.without(this.filterStates, filter)
209
+ this.filterStates = Arrays.without(this.filterStates, filter)
209
210
  this.updateFilterEditors()
210
211
  }
211
212
  }
@@ -336,7 +337,7 @@ class FilterEditorContainer extends Part<FilterState> {
336
337
 
337
338
  class DirectFilterEditor extends FilterEditor<DirectFilter> {
338
339
 
339
- numericChangeKey = messages.untypedKey()
340
+ numericChangeKey = Messages.untypedKey()
340
341
 
341
342
  async init() {
342
343
  await super.init()
@@ -399,7 +400,7 @@ class DirectFilterEditor extends FilterEditor<DirectFilter> {
399
400
  // Inclusion Editor
400
401
  ////////////////////////////////////////////////////////////////////////////////
401
402
 
402
- const inclusionChangedKey = messages.typedKey<{value: string}>()
403
+ const inclusionChangedKey = Messages.typedKey<{value: string}>()
403
404
 
404
405
  class InclusionFilterEditor extends FilterEditor<InclusionFilter> {
405
406
 
@@ -456,9 +457,9 @@ class InclusionFilterEditor extends FilterEditor<InclusionFilter> {
456
457
  // Date Range Editor
457
458
  ////////////////////////////////////////////////////////////////////////////////
458
459
 
459
- const dateRangeRelativeChangedKey = messages.untypedKey()
460
- const dateRangePeriodChangedKey = messages.typedKey<{period: string}>()
461
- const dateRangePreselectKey = messages.typedKey<VirtualDateRange>()
460
+ const dateRangeRelativeChangedKey = Messages.untypedKey()
461
+ const dateRangePeriodChangedKey = Messages.typedKey<{period: string}>()
462
+ const dateRangePreselectKey = Messages.typedKey<VirtualDateRange>()
462
463
 
463
464
  class DateRangeFilterEditor extends FilterEditor<DateRangeFilter> {
464
465
 
@@ -550,7 +551,7 @@ class DateRangeFilterEditor extends FilterEditor<DateRangeFilter> {
550
551
 
551
552
  type AddFilterCallback = (filter: Filter) => any
552
553
 
553
- const columnSelectedKey = messages.typedKey<{column: string}>()
554
+ const columnSelectedKey = Messages.typedKey<{column: string}>()
554
555
 
555
556
  class AddFilterDropdown extends Dropdown<{modelDef: ModelDef, callback: AddFilterCallback}> {
556
557
  columns!: ColumnDef[]
@@ -562,7 +563,7 @@ class AddFilterDropdown extends Dropdown<{modelDef: ModelDef, callback: AddFilte
562
563
  async init() {
563
564
  await super.init()
564
565
 
565
- this.columns = arrays.sortByFunction(Object.values(this.state.modelDef.columns), col => {
566
+ this.columns = Arrays.sortByFunction(Object.values(this.state.modelDef.columns), col => {
566
567
  const visibility = col.metadata?.visibility
567
568
  const sort = visibility == 'common' ? '0' : '1'
568
569
  return `${sort}${col.name}`
@@ -6,9 +6,11 @@ import dayjs from "dayjs"
6
6
  import QueryEditor from "./query-editor"
7
7
  import TerrierPart from "../../terrier/parts/terrier-part"
8
8
  import Schema, {ModelDef, SchemaDef} from "../../terrier/schema"
9
- import {arrays, messages, strings} from "tuff-core"
10
9
  import {Logger} from "tuff-core/logging"
11
- import inflection from "inflection";
10
+ import inflection from "inflection"
11
+ import Messages from "tuff-core/messages"
12
+ import Strings from "tuff-core/strings"
13
+ import Arrays from "tuff-core/arrays"
12
14
 
13
15
  const log = new Logger("Queries")
14
16
 
@@ -50,24 +52,19 @@ async function validate(query: Query): Promise<QueryValidation> {
50
52
  }
51
53
 
52
54
 
53
- ////////////////////////////////////////////////////////////////////////////////
54
- // Inputs
55
- ////////////////////////////////////////////////////////////////////////////////
56
-
57
-
58
-
59
55
  ////////////////////////////////////////////////////////////////////////////////
60
56
  // Preview
61
57
  ////////////////////////////////////////////////////////////////////////////////
62
58
 
63
59
  export type QueryResultRow = Record<string, any>
64
60
 
65
- const ColumnTypes = ['string', 'integer', 'float', 'date', 'time', 'dollars'] as const
61
+ const ColumnTypes = ['string', 'integer', 'float', 'date', 'datetime', 'cents', 'dollars'] as const
66
62
 
67
63
  export type ColumnType = typeof ColumnTypes[number]
68
64
 
69
65
  export type QueryResultColumn = {
70
- name: string
66
+ select_name: string
67
+ column_name: string
71
68
  type: ColumnType
72
69
  }
73
70
 
@@ -102,15 +99,19 @@ function renderCell(td: TableCellTag, col: QueryResultColumn, val: any): any {
102
99
  case 'date':
103
100
  const date = dayjs(val.toString())
104
101
  return td.div('.date').text(date.format(DateFormat))
105
- case 'time':
102
+ case 'datetime':
106
103
  const time = dayjs(val.toString())
107
104
  td.div('.date').text(time.format(DateFormat))
108
105
  return td.div('.time').text(time.format(TimeFormat))
109
106
  case 'dollars':
110
107
  const dollars = parseFloat(val)
111
108
  return td.div('.dollars').text(`\$${dollars}`)
109
+ case 'cents':
110
+ const cents = parseInt(val)
111
+ const d = (cents/100.0).toFixed(2)
112
+ return td.div('.dollars').text(`\$${d}`)
112
113
  case 'string':
113
- if (col.name.endsWith('id')) {
114
+ if (col.select_name.endsWith('id')) {
114
115
  const id = val.toString()
115
116
  td.a('.id')
116
117
  .data({tooltip: id})
@@ -134,7 +135,7 @@ function renderTable(parent: PartTag, rows: QueryResultRow[], columns: QueryResu
134
135
  thead.tr(tr => {
135
136
  for (const col of columns) {
136
137
  tr.th(col.type, th => {
137
- th.a().text(col.name)
138
+ th.a().text(col.select_name)
138
139
  })
139
140
  }
140
141
  })
@@ -146,7 +147,7 @@ function renderTable(parent: PartTag, rows: QueryResultRow[], columns: QueryResu
146
147
  tbody.tr(tr => {
147
148
  for (const col of columns) {
148
149
  tr.td(td => {
149
- const val = row[col.name]
150
+ const val = row[col.select_name]
150
151
  if (val) {
151
152
  renderCell(td, col, val)
152
153
  }
@@ -185,7 +186,7 @@ export type QueryModelPickerState = {
185
186
  */
186
187
  export class QueryModelPicker extends TerrierPart<QueryModelPickerState> {
187
188
 
188
- pickedKey = messages.typedKey<{ model: string }>()
189
+ pickedKey = Messages.typedKey<{ model: string }>()
189
190
  model?: ModelDef
190
191
 
191
192
  async init() {
@@ -206,7 +207,7 @@ export class QueryModelPicker extends TerrierPart<QueryModelPickerState> {
206
207
  label.input({type: 'radio', name: `new-query-model-${this.id}`, value: model.name})
207
208
  .emitChange(this.pickedKey, {model: model.name})
208
209
  label.div(col => {
209
- const name = inflection.pluralize(strings.titleize(model.name))
210
+ const name = inflection.pluralize(Strings.titleize(model.name))
210
211
  col.div('.name').text(name)
211
212
  if (model.metadata?.description) {
212
213
  col.div('.description').text(model.metadata.description)
@@ -229,7 +230,7 @@ export class QueryModelPicker extends TerrierPart<QueryModelPickerState> {
229
230
  h3.i('.glyp-refresh')
230
231
  h3.span().text("Common Tables")
231
232
  })
232
- for (const model of arrays.sortBy(commonModels, 'name')) {
233
+ for (const model of Arrays.sortBy(commonModels, 'name')) {
233
234
  this.renderModelOption(col, model)
234
235
  }
235
236
  }
@@ -242,7 +243,7 @@ export class QueryModelPicker extends TerrierPart<QueryModelPickerState> {
242
243
  h3.i('.glyp-pending')
243
244
  h3.span().text("Other Tables")
244
245
  })
245
- for (const model of arrays.sortBy(uncommonModels, 'name')) {
246
+ for (const model of Arrays.sortBy(uncommonModels, 'name')) {
246
247
  this.renderModelOption(col, model)
247
248
  }
248
249
  }
@@ -5,10 +5,10 @@ import {Logger} from "tuff-core/logging"
5
5
  import QueryForm, {QuerySettings, QuerySettingsColumns} from "./query-form"
6
6
  import DiveEditor, {DiveEditorState} from "../dives/dive-editor"
7
7
  import Objects from "tuff-core/objects"
8
- import {messages} from "tuff-core"
9
8
  import Html from "tuff-core/html"
10
9
  import ContentPart from "../../terrier/parts/content-part"
11
10
  import {TabContainerPart} from "../../terrier/tabs"
11
+ import Messages from "tuff-core/messages"
12
12
 
13
13
  const log = new Logger("QueryEditor")
14
14
 
@@ -17,7 +17,7 @@ const log = new Logger("QueryEditor")
17
17
  // Keys
18
18
  ////////////////////////////////////////////////////////////////////////////////
19
19
 
20
- const validationKey = messages.typedKey<QueryValidation>()
20
+ const validationKey = Messages.typedKey<QueryValidation>()
21
21
 
22
22
 
23
23
  ////////////////////////////////////////////////////////////////////////////////
@@ -157,7 +157,7 @@ export default class QueryEditor extends ContentPart<QueryEditorState> {
157
157
  sqlPart!: SqlPart
158
158
  previewPart!: PreviewPart
159
159
 
160
- updatePreviewKey = messages.untypedKey()
160
+ updatePreviewKey = Messages.untypedKey()
161
161
 
162
162
  async init() {
163
163
  const query = this.state.query
@@ -231,5 +231,5 @@ export default class QueryEditor extends ContentPart<QueryEditorState> {
231
231
  this.tabs.showTab('preview')
232
232
  }
233
233
 
234
- static readonly copyToClipboardKey = messages.typedKey<{ value: string }>()
234
+ static readonly copyToClipboardKey = Messages.typedKey<{ value: string }>()
235
235
  }
@@ -2,8 +2,8 @@ import {Query} from "./queries"
2
2
  import {PartTag} from "tuff-core/parts"
3
3
  import {FormFields} from "tuff-core/forms"
4
4
  import {Logger} from "tuff-core/logging"
5
- import {messages} from "tuff-core"
6
- import ContentPart from "../../terrier/parts/content-part";
5
+ import ContentPart from "../../terrier/parts/content-part"
6
+ import Messages from "tuff-core/messages"
7
7
 
8
8
  const log = new Logger("QueryForm")
9
9
 
@@ -46,6 +46,6 @@ export default class QueryForm extends ContentPart<{ query: QuerySettings }> {
46
46
  })
47
47
  }
48
48
 
49
- static readonly settingsChangedKey = messages.typedKey<QuerySettings>()
49
+ static readonly settingsChangedKey = Messages.typedKey<QuerySettings>()
50
50
 
51
51
  }
@@ -3,13 +3,14 @@ import Schema, {BelongsToDef, ModelDef, SchemaDef} from "../../terrier/schema"
3
3
  import inflection from "inflection"
4
4
  import Filters, {Filter, FilterInput, FiltersEditorModal} from "./filters"
5
5
  import Columns, {ColumnRef, ColumnsEditorModal} from "./columns"
6
- import {arrays, messages} from "tuff-core"
7
6
  import {Logger} from "tuff-core/logging"
8
7
  import ContentPart from "../../terrier/parts/content-part"
9
8
  import {ActionsDropdown} from "../../terrier/dropdowns"
10
9
  import {ModalPart} from "../../terrier/modals"
11
10
  import TerrierFormPart from "../../terrier/parts/terrier-form-part"
12
- import DiveEditor from "../dives/dive-editor";
11
+ import DiveEditor from "../dives/dive-editor"
12
+ import Messages from "tuff-core/messages"
13
+ import Arrays from "tuff-core/arrays"
13
14
 
14
15
  const log = new Logger("Tables")
15
16
 
@@ -35,7 +36,7 @@ export type JoinedTableRef = TableRef & {
35
36
  // Keys
36
37
  ////////////////////////////////////////////////////////////////////////////////
37
38
 
38
- const updatedKey = messages.typedKey<TableRef>()
39
+ const updatedKey = Messages.typedKey<TableRef>()
39
40
 
40
41
 
41
42
  ////////////////////////////////////////////////////////////////////////////////
@@ -75,11 +76,11 @@ export class TableView<T extends TableRef> extends ContentPart<{ schema: SchemaD
75
76
  modelDef!: ModelDef
76
77
  parentView?: TableView<any>
77
78
 
78
- editTableKey = messages.untypedKey()
79
- editColumnsKey = messages.untypedKey()
80
- editFiltersKey = messages.untypedKey()
81
- newJoinedKey = messages.untypedKey()
82
- createJoinedKey = messages.typedKey<{name: string}>()
79
+ editTableKey = Messages.untypedKey()
80
+ editColumnsKey = Messages.untypedKey()
81
+ editFiltersKey = Messages.untypedKey()
82
+ newJoinedKey = Messages.untypedKey()
83
+ createJoinedKey = Messages.typedKey<{name: string}>()
83
84
 
84
85
  async init() {
85
86
  this.schema = this.state.schema
@@ -111,7 +112,7 @@ export class TableView<T extends TableRef> extends ContentPart<{ schema: SchemaD
111
112
 
112
113
  // show the common tables at the top
113
114
  let showingCommon = true
114
- const actions = arrays.sortByFunction(newJoins, bt => {
115
+ const actions = Arrays.sortByFunction(newJoins, bt => {
115
116
  const model = this.schema.models[bt.model]
116
117
  const common = model.metadata?.visibility == 'common' ? '0' : '1'
117
118
  return `${common}${bt.name}`
@@ -394,7 +395,7 @@ class JoinedTableEditorForm extends TerrierFormPart<JoinedTableRef> {
394
395
  class JoinedTableEditorModal extends ModalPart<JoinedTableEditorState> {
395
396
 
396
397
  form!: JoinedTableEditorForm
397
- applyKey = messages.untypedKey()
398
+ applyKey = Messages.untypedKey()
398
399
 
399
400
  async init() {
400
401
  this.form = this.makePart(JoinedTableEditorForm, this.state.table)
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "files": [
5
5
  "*"
6
6
  ],
7
- "version": "4.5.3",
7
+ "version": "4.7.0",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "https://github.com/Terrier-Tech/terrier-engine"
package/terrier/api.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Logger } from "tuff-core/logging"
2
2
  import { QueryParams } from "tuff-core/urls"
3
+ import {LogEntry} from "./logging"
3
4
 
4
5
  const log = new Logger('Api')
5
6
  log.level = 'debug'
@@ -113,17 +114,6 @@ async function post<ResponseType>(url: string, body: Record<string, unknown> | F
113
114
  // Event Streams
114
115
  ////////////////////////////////////////////////////////////////////////////////
115
116
 
116
- type LogLevel = 'success' | 'info' | 'warn' | 'debug'
117
-
118
- /**
119
- * Type of log events from a streaming response.
120
- */
121
- export type LogEvent = {
122
- level: LogLevel
123
- prefix?: string
124
- message: string
125
- }
126
-
127
117
  /**
128
118
  * Type of error events from a streaming response.
129
119
  */
@@ -188,8 +178,8 @@ export class Streamer {
188
178
  * Listen specifically for log events.
189
179
  * @param listener
190
180
  */
191
- onLog(listener: (event: LogEvent) => any) {
192
- return this.on<LogEvent>('_log', listener)
181
+ onLog(listener: (event: LogEntry) => any) {
182
+ return this.on<LogEntry>('_log', listener)
193
183
  }
194
184
 
195
185
  /**
package/terrier/app.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Logger } from "tuff-core/logging"
1
+ import {Logger} from "tuff-core/logging"
2
2
  import {Part, PartConstructor, PartParent} from "tuff-core/parts"
3
3
  import TerrierPart from "./parts/terrier-part"
4
4
  import Tooltips from "./tooltips"
@@ -6,11 +6,12 @@ import Lightbox from "./lightbox"
6
6
  import Theme from "./theme"
7
7
  import {ModalPart, ModalStackPart} from "./modals"
8
8
  import {OverlayLayerType, OverlayPart} from "./overlays"
9
+ import Messages from "tuff-core/messages"
9
10
 
10
11
  // @ts-ignore
11
12
  import logoUrl from './images/optimized/terrier-hub-logo-light.svg'
12
- import Sheets, {AlertSheetState, ConfirmSheetState, Sheet, SheetState} from "./sheets";
13
- import {messages} from "tuff-core";
13
+ import Sheets, {AlertSheetState, ConfirmSheetState, Sheet, SheetState} from "./sheets"
14
+
14
15
 
15
16
  const log = new Logger('App')
16
17
  Logger.level = 'info'
@@ -103,7 +104,7 @@ export abstract class TerrierApp<TState> extends TerrierPart<TState> {
103
104
  * @param callback gets called if the user hits "Confirm"
104
105
  */
105
106
  confirm(options: ConfirmSheetState, callback: () => any) {
106
- const key = messages.untypedKey()
107
+ const key = Messages.untypedKey()
107
108
  const state = {...options,
108
109
  primaryActions: [
109
110
  {
@@ -1,15 +1,15 @@
1
1
  import {Logger} from "tuff-core/logging"
2
- import {untypedKey} from "tuff-core/messages"
3
- import {unique} from "tuff-core/arrays"
2
+ import Messages from "tuff-core/messages"
4
3
  import {PartTag, StatelessPart} from "tuff-core/parts"
5
4
  import Overlays from "./overlays"
6
5
  import TerrierPart from "./parts/terrier-part"
7
6
  import Objects from "tuff-core/objects"
8
7
  import {Action} from "./theme"
8
+ import Arrays from "tuff-core/arrays"
9
9
 
10
10
  const log = new Logger('Dropdowns')
11
11
 
12
- const clearDropdownKey = untypedKey()
12
+ const clearDropdownKey = Messages.untypedKey()
13
13
 
14
14
  /**
15
15
  * Abstract base class for dropdown parts.
@@ -77,7 +77,7 @@ export abstract class Dropdown<TState> extends TerrierPart<TState> {
77
77
  }
78
78
 
79
79
  update(_elem: HTMLElement) {
80
- const content = _elem.querySelector('.tt-dropdown-content')
80
+ const content: HTMLElement | null = _elem.querySelector('.tt-dropdown-content')
81
81
  if (content) {
82
82
  if (this.anchorTarget) {
83
83
  log.info(`Anchoring dropdown`, content, this.anchorTarget)
@@ -109,7 +109,7 @@ export class ActionsDropdown extends Dropdown<Array<Action>> {
109
109
  renderContent(parent: PartTag) {
110
110
  // handle each key declared on the actions directly,
111
111
  // then clear the dropdown and re-emit them on the parent part
112
- const keys = unique(this.state.map(action => action.click?.key).filter(Objects.notNull))
112
+ const keys = Arrays.unique(this.state.map(action => action.click?.key).filter(Objects.notNull))
113
113
  for (const key of keys) {
114
114
  this.onClick(key, m => {
115
115
  this.clear()
package/terrier/forms.ts CHANGED
@@ -1,13 +1,15 @@
1
1
  import {Field, FormFields, FormPartData, InputType, KeyOfType, SelectOptions} from "tuff-core/forms"
2
- import {logging, messages, strings} from "tuff-core"
3
2
  import {DbErrors} from "./db-client"
4
3
  import {PartTag} from "tuff-core/parts"
5
4
  import {InputTag, InputTagAttrs} from "tuff-core/html"
6
5
  import TerrierPart from "./parts/terrier-part"
7
6
  import GlypPicker from "./parts/glyp-picker"
8
7
  import Glyps from "./glyps"
8
+ import Messages from "tuff-core/messages"
9
+ import {Logger} from "tuff-core/logging"
10
+ import Strings from "tuff-core/strings"
9
11
 
10
- const log = new logging.Logger("TerrierForms")
12
+ const log = new Logger("TerrierForms")
11
13
 
12
14
  ////////////////////////////////////////////////////////////////////////////////
13
15
  // Options
@@ -19,7 +21,7 @@ const log = new logging.Logger("TerrierForms")
19
21
  */
20
22
  function titleizeOptions(opts: string[], blank?: string): SelectOptions {
21
23
  const out = opts.map(c => {
22
- return {value: c, title: strings.titleize(c)}
24
+ return {value: c, title: Strings.titleize(c)}
23
25
  })
24
26
  if (blank != undefined) { // don't test length, allow it to be a blank string
25
27
  out.unshift({title: blank, value: ''})
@@ -39,7 +41,7 @@ export class TerrierFormFields<T extends FormPartData> extends FormFields<T> {
39
41
 
40
42
  errors?: DbErrors<T>
41
43
 
42
- pickGlypKey = messages.typedKey<{ key: KeyOfType<T, string | undefined> & string }>()
44
+ pickGlypKey = Messages.typedKey<{ key: KeyOfType<T, string | undefined> & string }>()
43
45
 
44
46
  /**
45
47
  * You must pass a `TerrierPart` so that we can render the error bubble with it.
package/terrier/glyps.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  // This file was automatically generated by glyps:compile on 08/19/23 9:07 AM, DO NOT MANUALLY EDIT!
2
2
 
3
- import {strings} from "tuff-core"
3
+
4
+ import Strings from "tuff-core/strings"
4
5
 
5
6
  const names = ['glyp-abacus', 'glyp-account', 'glyp-accounts_payable', 'glyp-accounts_receivable', 'glyp-action_log', 'glyp-activate', 'glyp-active', 'glyp-activity_high', 'glyp-activity_low', 'glyp-activity_medium', 'glyp-activity_none', 'glyp-address1', 'glyp-adjustment', 'glyp-admin', 'glyp-administration', 'glyp-air_freshener', 'glyp-alert', 'glyp-align_horizontal', 'glyp-align_vertical', 'glyp-anchor', 'glyp-announcement', 'glyp-app_update', 'glyp-append_selection', 'glyp-appointment', 'glyp-appointment_setup', 'glyp-archive', 'glyp-arrow_down', 'glyp-arrow_left', 'glyp-arrow_right', 'glyp-arrow_transfer', 'glyp-arrow_up', 'glyp-ascending', 'glyp-attachment', 'glyp-audit_analyzer', 'glyp-autopay', 'glyp-availability', 'glyp-background_batch', 'glyp-background_job', 'glyp-bait_for_demolition', 'glyp-bar_chart', 'glyp-barcode', 'glyp-bat_exclusion', 'glyp-bed_bug_fumigation', 'glyp-begin_service', 'glyp-belongs_to', 'glyp-billing', 'glyp-billing_office', 'glyp-billing_terms', 'glyp-billto', 'glyp-bioremediation', 'glyp-bird_exclusion', 'glyp-black_light', 'glyp-branch', 'glyp-branch_bats', 'glyp-branch_birds', 'glyp-branch_general', 'glyp-branch_irrigation', 'glyp-branch_landscape', 'glyp-branch_multi_housing', 'glyp-branch_office', 'glyp-branch_orders', 'glyp-branch_residential', 'glyp-branch_sales', 'glyp-branch_termites', 'glyp-branch_weeds', 'glyp-branch_wildlife', 'glyp-build', 'glyp-calculate', 'glyp-calendar', 'glyp-calendar_branch', 'glyp-calendar_map', 'glyp-calendar_rolling', 'glyp-calendar_share', 'glyp-calendar_snap', 'glyp-california', 'glyp-call', 'glyp-callback', 'glyp-camera', 'glyp-cancellation', 'glyp-cancelled', 'glyp-card_american_express', 'glyp-card_discover', 'glyp-card_mastercard', 'glyp-card_visa', 'glyp-catalog', 'glyp-caught', 'glyp-cert', 'glyp-check_all', 'glyp-check_in', 'glyp-check_in_service', 'glyp-checked', 'glyp-checked_empty', 'glyp-checkmark', 'glyp-checkmark_filled', 'glyp-checkout', 'glyp-chemical', 'glyp-chemical_authorization', 'glyp-chemical_backpack_sprayer', 'glyp-chemical_biological_controls', 'glyp-chemical_disinfectant', 'glyp-chemical_fertilizer', 'glyp-chemical_flying_insect_bait', 'glyp-chemical_fumigants', 'glyp-chemical_insect_aerosol', 'glyp-chemical_insect_bait', 'glyp-chemical_insect_dust', 'glyp-chemical_insect_granules', 'glyp-chemical_insect_spray', 'glyp-chemical_label', 'glyp-chemical_mole_bait', 'glyp-chemical_pest_bird_control', 'glyp-chemical_pheromone_products', 'glyp-chemical_power_sprayer', 'glyp-chemical_rodenticide_block', 'glyp-chemical_rodenticide_non_block', 'glyp-chemical_sds', 'glyp-chemical_tree_products', 'glyp-chemical_weed_control', 'glyp-chemical_wildlife_repellents', 'glyp-chemical_wildlife_toxicants', 'glyp-chevron_down', 'glyp-chevron_left', 'glyp-chevron_right', 'glyp-chevron_up', 'glyp-city', 'glyp-click', 'glyp-client_actions', 'glyp-client_pest_sightings', 'glyp-client_required', 'glyp-close', 'glyp-cloud', 'glyp-clypboard', 'glyp-clypedia', 'glyp-clypmart', 'glyp-code', 'glyp-code_details', 'glyp-collections', 'glyp-columns', 'glyp-comment', 'glyp-comment_filled', 'glyp-commission_origin', 'glyp-commissions', 'glyp-company_setup', 'glyp-compass', 'glyp-complete', 'glyp-complete_certificate', 'glyp-complete_enrollment', 'glyp-condition_appliance_machinery', 'glyp-condition_customer', 'glyp-condition_device', 'glyp-condition_door_window', 'glyp-condition_dumpster', 'glyp-condition_entry_point', 'glyp-condition_evidence', 'glyp-condition_exclusion', 'glyp-condition_exterior', 'glyp-condition_interior', 'glyp-condition_plumbing', 'glyp-condition_prep_storage', 'glyp-condition_rodent_evidence', 'glyp-condition_rodent_exclusion', 'glyp-condition_roof_ceiling', 'glyp-condition_structure', 'glyp-condition_supplies', 'glyp-condition_termites', 'glyp-condition_ventilation', 'glyp-condition_wall_floor', 'glyp-condition_wildlife', 'glyp-conditions', 'glyp-consolidate', 'glyp-construction', 'glyp-contract', 'glyp-contract_cancellation', 'glyp-contract_overview', 'glyp-conversation', 'glyp-copesan', 'glyp-copy', 'glyp-credit_memo', 'glyp-credit_non_revenue', 'glyp-credit_production', 'glyp-credit_prospect', 'glyp-credit_sales_bonus', 'glyp-crew', 'glyp-cumulative', 'glyp-cursor', 'glyp-custom_form', 'glyp-customer', 'glyp-customer_service', 'glyp-dashboard', 'glyp-data_dive', 'glyp-data_dive_query', 'glyp-data_dives', 'glyp-database', 'glyp-dead_animal_removal', 'glyp-default', 'glyp-delete', 'glyp-demo_mode', 'glyp-descending', 'glyp-design', 'glyp-desktop', 'glyp-detail_report', 'glyp-developer', 'glyp-device', 'glyp-device_sync', 'glyp-differential', 'glyp-disable', 'glyp-disabled_filled', 'glyp-distribute_horizontal', 'glyp-distribute_vertical', 'glyp-division', 'glyp-document', 'glyp-documentation', 'glyp-documents', 'glyp-download', 'glyp-driving', 'glyp-duplicate', 'glyp-duplicate_location', 'glyp-duration', 'glyp-edit', 'glyp-email', 'glyp-employment', 'glyp-entertainment_public_venues', 'glyp-equipment_bear_trap', 'glyp-equipment_flying_insect_bait_station', 'glyp-equipment_flying_insect_light_trap', 'glyp-equipment_insect_monitor', 'glyp-equipment_lethal_animal_trap', 'glyp-equipment_live_animal_trap', 'glyp-equipment_live_rodent_trap', 'glyp-equipment_maintenance', 'glyp-equipment_map_viewer', 'glyp-equipment_maps', 'glyp-equipment_mosquito_trap', 'glyp-equipment_other', 'glyp-equipment_pheromone_trap', 'glyp-equipment_pick_up', 'glyp-equipment_rodent_bait', 'glyp-equipment_rodent_trap', 'glyp-equipment_termite_bait_station', 'glyp-equipment_termite_monitor', 'glyp-exclusion', 'glyp-expand', 'glyp-expired', 'glyp-expiring', 'glyp-exterior', 'glyp-extras', 'glyp-facebook', 'glyp-farm', 'glyp-farm_grain_seed', 'glyp-fast_and_frequent', 'glyp-favorite', 'glyp-feedback', 'glyp-field_guide', 'glyp-field_training', 'glyp-file_blank', 'glyp-file_html', 'glyp-file_image', 'glyp-file_pdf', 'glyp-file_printable', 'glyp-file_spreadsheet', 'glyp-file_text', 'glyp-filter', 'glyp-finance', 'glyp-finding', 'glyp-folder', 'glyp-followup', 'glyp-food_bev_pharma', 'glyp-formula', 'glyp-fuel', 'glyp-fumigation', 'glyp-function', 'glyp-function_aggregate', 'glyp-function_time', 'glyp-gain_loss', 'glyp-generate_invoices', 'glyp-generate_orders', 'glyp-geo', 'glyp-geo_heat_map', 'glyp-google', 'glyp-google_calendar', 'glyp-government', 'glyp-grain_seed', 'glyp-grid', 'glyp-grouped', 'glyp-grouping_combine', 'glyp-grouping_separate', 'glyp-has_many', 'glyp-health_care', 'glyp-heat_map', 'glyp-heat_treatment', 'glyp-help', 'glyp-hidden', 'glyp-hint', 'glyp-honeybee_removal', 'glyp-housing', 'glyp-in_progress', 'glyp-inbox', 'glyp-incomplete_certificate', 'glyp-industrial', 'glyp-info', 'glyp-information_technology', 'glyp-inspect_map', 'glyp-inspections', 'glyp-insulation', 'glyp-interior', 'glyp-invoice', 'glyp-invoice_review', 'glyp-irrigation_install', 'glyp-irrigation_maintenance', 'glyp-items', 'glyp-job', 'glyp-join', 'glyp-join_inner', 'glyp-join_left', 'glyp-justice', 'glyp-k9_inspection', 'glyp-key', 'glyp-label_bottom', 'glyp-label_left', 'glyp-label_right', 'glyp-label_top', 'glyp-labor', 'glyp-landscape', 'glyp-landscape_maintenance', 'glyp-laptop', 'glyp-lead', 'glyp-lead_listing', 'glyp-lead_source', 'glyp-leads_report', 'glyp-learning_hub', 'glyp-ledger', 'glyp-legal', 'glyp-license', 'glyp-lifetime', 'glyp-line_cap_arrow', 'glyp-line_cap_bar', 'glyp-line_cap_circle', 'glyp-line_cap_none', 'glyp-line_caps', 'glyp-line_style', 'glyp-link', 'glyp-location', 'glyp-location_charge', 'glyp-location_credit', 'glyp-location_kind', 'glyp-location_message', 'glyp-location_origin', 'glyp-location_tags', 'glyp-locations', 'glyp-locked', 'glyp-lodging', 'glyp-log_in', 'glyp-log_out', 'glyp-log_report', 'glyp-logbook', 'glyp-logbook_documents', 'glyp-lot_number', 'glyp-manager', 'glyp-map', 'glyp-markdown', 'glyp-market', 'glyp-materials', 'glyp-mattress_encasement', 'glyp-merge', 'glyp-message_billing', 'glyp-message_collections', 'glyp-message_complaint', 'glyp-message_misc', 'glyp-message_technician', 'glyp-messages', 'glyp-misc', 'glyp-miscellaneous', 'glyp-move_order', 'glyp-mowing', 'glyp-multi_housing', 'glyp-mute', 'glyp-navicon', 'glyp-new_location', 'glyp-new_ticket', 'glyp-no_charge', 'glyp-no_service', 'glyp-note', 'glyp-note_access', 'glyp-note_billing', 'glyp-note_collection', 'glyp-note_complaint', 'glyp-note_directions', 'glyp-note_focused', 'glyp-note_office', 'glyp-note_preservice', 'glyp-note_sales', 'glyp-note_service', 'glyp-notification', 'glyp-number', 'glyp-office', 'glyp-office_government', 'glyp-office_training', 'glyp-on_demand', 'glyp-on_demand_order', 'glyp-open', 'glyp-open_invoice', 'glyp-operations', 'glyp-options', 'glyp-order_actions', 'glyp-order_approved', 'glyp-order_batch', 'glyp-order_editor', 'glyp-order_pending', 'glyp-order_series', 'glyp-order_unapproved', 'glyp-org_structure', 'glyp-org_unit', 'glyp-org_unit_settings', 'glyp-origin', 'glyp-origin_client', 'glyp-origin_tech', 'glyp-outbox', 'glyp-outlook', 'glyp-overlap', 'glyp-password', 'glyp-past_due', 'glyp-paused', 'glyp-pay_brackets', 'glyp-payment', 'glyp-payment_ach', 'glyp-payment_application_apply', 'glyp-payment_application_correction', 'glyp-payment_application_initial', 'glyp-payment_application_refund', 'glyp-payment_application_transfer', 'glyp-payment_application_unapply', 'glyp-payment_application_unapply_all', 'glyp-payment_applications', 'glyp-payment_batch', 'glyp-payment_cash', 'glyp-payment_check', 'glyp-payment_coupon', 'glyp-payment_credit', 'glyp-payment_discount', 'glyp-payment_eft', 'glyp-payment_finance_charge', 'glyp-payments', 'glyp-payroll', 'glyp-pdf', 'glyp-pending', 'glyp-periodic', 'glyp-periodic_assessment', 'glyp-permission', 'glyp-pest', 'glyp-pest_ants', 'glyp-pest_bats', 'glyp-pest_bed_bugs', 'glyp-pest_birds', 'glyp-pest_crawling_insects', 'glyp-pest_dermestids', 'glyp-pest_fall_invaders', 'glyp-pest_flying_insects', 'glyp-pest_moles', 'glyp-pest_mosquitoes', 'glyp-pest_nuisance_animals', 'glyp-pest_ornamental', 'glyp-pest_ornamental_diseases', 'glyp-pest_roaches', 'glyp-pest_rodents', 'glyp-pest_scorpions', 'glyp-pest_snakes', 'glyp-pest_spiders', 'glyp-pest_stinging_insects', 'glyp-pest_stored_product_pests', 'glyp-pest_ticks_and_fleas', 'glyp-pest_viruses_and_bacteria', 'glyp-pest_weeds', 'glyp-pest_wood_destroyers', 'glyp-phone', 'glyp-pick_date', 'glyp-pick_list', 'glyp-platform_android', 'glyp-platform_ios', 'glyp-platform_macos', 'glyp-platform_windows', 'glyp-play', 'glyp-plus', 'glyp-plus_filled', 'glyp-plus_outline', 'glyp-portal', 'glyp-price_increase', 'glyp-price_increase_alt', 'glyp-pricing_table', 'glyp-print', 'glyp-privacy', 'glyp-product_sale', 'glyp-production', 'glyp-professional_consultation', 'glyp-program', 'glyp-program_elements', 'glyp-program_initiation', 'glyp-program_order', 'glyp-program_review', 'glyp-promo', 'glyp-proposal', 'glyp-protection', 'glyp-purchase_order', 'glyp-quality', 'glyp-query', 'glyp-radio_checked', 'glyp-radio_empty', 'glyp-reassign', 'glyp-receipt', 'glyp-recent', 'glyp-recommendation', 'glyp-record_details', 'glyp-recurring_revenue', 'glyp-redo', 'glyp-refresh', 'glyp-refund', 'glyp-region', 'glyp-released', 'glyp-remove', 'glyp-renewal', 'glyp-report', 'glyp-required_input', 'glyp-reschedule', 'glyp-restaurant_bar', 'glyp-revenue', 'glyp-review', 'glyp-rfid', 'glyp-ride_along', 'glyp-rodent_exclusion', 'glyp-route_change', 'glyp-route_optimization', 'glyp-rows', 'glyp-ruler', 'glyp-sales', 'glyp-sales_contest', 'glyp-sales_pipeline', 'glyp-sales_tax', 'glyp-sales_tax_exemption', 'glyp-schedule_lock', 'glyp-schedule_lock_afternoon', 'glyp-schedule_lock_day', 'glyp-schedule_lock_exact', 'glyp-schedule_lock_four_hour', 'glyp-schedule_lock_morning', 'glyp-schedule_lock_none', 'glyp-schedule_lock_two_day', 'glyp-schedule_lock_two_hour', 'glyp-schedule_lock_week', 'glyp-schedule_tyoe_every', 'glyp-schedule_type_every', 'glyp-schedule_type_none', 'glyp-schedule_type_on_demand', 'glyp-schedule_type_schedule', 'glyp-scheduled', 'glyp-scheduling', 'glyp-school_church', 'glyp-script', 'glyp-search', 'glyp-seeding', 'glyp-segment', 'glyp-sent', 'glyp-sentricon', 'glyp-service_agreement', 'glyp-service_form', 'glyp-service_miscellaneous', 'glyp-service_reminder', 'glyp-service_report', 'glyp-service_request', 'glyp-services', 'glyp-settings', 'glyp-setup', 'glyp-signature', 'glyp-simulator', 'glyp-site_map', 'glyp-site_map_annotation', 'glyp-site_map_chemical', 'glyp-site_map_edit_details', 'glyp-site_map_edit_geo', 'glyp-site_map_equipment', 'glyp-site_map_exterior', 'glyp-site_map_foundation', 'glyp-site_map_icon', 'glyp-site_map_interior', 'glyp-site_map_label', 'glyp-site_map_perimeter', 'glyp-site_map_settings_left', 'glyp-site_map_settings_right', 'glyp-site_map_template', 'glyp-skip', 'glyp-smart_traps', 'glyp-sms', 'glyp-snow_removal', 'glyp-sort', 'glyp-special', 'glyp-split', 'glyp-spp_scorecard', 'glyp-square_edge', 'glyp-start_sheet', 'glyp-start_time', 'glyp-statement', 'glyp-states', 'glyp-sticky', 'glyp-stop_time', 'glyp-store_material', 'glyp-store_order', 'glyp-store_order_back_order', 'glyp-store_order_cancelled', 'glyp-store_order_complete', 'glyp-store_order_pending', 'glyp-store_order_pending_return', 'glyp-store_order_pre_order', 'glyp-store_order_returned', 'glyp-stores', 'glyp-styling', 'glyp-subscribe', 'glyp-supplemental', 'glyp-support', 'glyp-sync', 'glyp-table', 'glyp-tablet', 'glyp-tablets', 'glyp-tag_manager', 'glyp-tags', 'glyp-task_runs', 'glyp-tech_pest_sightings', 'glyp-technician', 'glyp-technician_approach', 'glyp-template', 'glyp-termite_fumigation', 'glyp-terrier', 'glyp-territory', 'glyp-text', 'glyp-thermometer', 'glyp-third_party_billing', 'glyp-ticket', 'glyp-ticket_type', 'glyp-tickets', 'glyp-tidy', 'glyp-time', 'glyp-time_entry', 'glyp-timeline', 'glyp-toolbox', 'glyp-tqi', 'glyp-tracker', 'glyp-tracking', 'glyp-training_course', 'glyp-treatment_split', 'glyp-trends', 'glyp-trip_charge', 'glyp-ui_type', 'glyp-unarchive', 'glyp-unchecked', 'glyp-undo', 'glyp-unfavorite', 'glyp-unit_sweep', 'glyp-unit_tag', 'glyp-university', 'glyp-unlocked', 'glyp-unmute', 'glyp-unscheduled', 'glyp-update_check_positions', 'glyp-upload', 'glyp-user', 'glyp-user_credit', 'glyp-user_tags', 'glyp-users', 'glyp-utility', 'glyp-vacation', 'glyp-vacuum', 'glyp-variant', 'glyp-vendor', 'glyp-versions', 'glyp-video', 'glyp-visible', 'glyp-warehouse', 'glyp-wdo', 'glyp-web_dusting', 'glyp-website', 'glyp-wildlife', 'glyp-wildlife_exclusion', 'glyp-winter_check', 'glyp-wizard', 'glyp-work', 'glyp-work_class', 'glyp-work_code', 'glyp-work_order', 'glyp-work_production', 'glyp-work_progress', 'glyp-work_yield', 'glyp-year', 'glyp-yield', 'glyp-zip_code', 'glyp-zone_adjacent', 'glyp-zone_check', 'glyp-zone_production', 'glyp-zone_remote', 'glyp-zoom_fit'] as const
6
7
 
@@ -9,7 +10,7 @@ const names = ['glyp-abacus', 'glyp-account', 'glyp-accounts_payable', 'glyp-acc
9
10
  * @param glyp
10
11
  */
11
12
  const displayName = (glyp: string): string => {
12
- return strings.titleize(glyp.replace('glyp-', ''))
13
+ return Strings.titleize(glyp.replace('glyp-', ''))
13
14
  }
14
15
 
15
16
  const Glyps = {
@@ -1,5 +1,5 @@
1
1
  import {Logger} from "tuff-core/logging"
2
- import {untypedKey} from "tuff-core/messages"
2
+ import Messages from "tuff-core/messages"
3
3
  import {Part, PartTag} from "tuff-core/parts"
4
4
  import {TerrierApp} from "./app"
5
5
 
@@ -52,7 +52,7 @@ function showPart(app: TerrierApp<any>, state: LightboxState) {
52
52
  app.addOverlay(LightboxPart, state, 'lightbox')
53
53
  }
54
54
 
55
- const closeKey = untypedKey()
55
+ const closeKey = Messages.untypedKey()
56
56
 
57
57
  class LightboxPart extends Part<LightboxState> {
58
58
 
@@ -0,0 +1,75 @@
1
+ import TerrierPart from "./parts/terrier-part"
2
+ import {NoState, PartTag} from "tuff-core/parts"
3
+
4
+ ////////////////////////////////////////////////////////////////////////////////
5
+ // Types
6
+ ////////////////////////////////////////////////////////////////////////////////
7
+
8
+ export type LogLevel = 'success' | 'info' | 'warn' | 'error' | 'debug'
9
+
10
+ /**
11
+ * Type of log events from a streaming response.
12
+ */
13
+ export type LogEntry = {
14
+ level: LogLevel
15
+ prefix?: string
16
+ message: string
17
+ }
18
+
19
+
20
+ ////////////////////////////////////////////////////////////////////////////////
21
+ // List Part
22
+ ////////////////////////////////////////////////////////////////////////////////
23
+
24
+ export class LogListPart extends TerrierPart<NoState> {
25
+
26
+ entries: LogEntry[] = []
27
+
28
+ async init() {
29
+ }
30
+
31
+ render(parent: PartTag): any {
32
+ parent.div('.tt-log-entries', entries => {
33
+ for (const entry of this.entries) {
34
+ this.renderEntry(entries, entry)
35
+ }
36
+ })
37
+ }
38
+
39
+ /**
40
+ * Pushes an entry to the list and renders it.
41
+ * @todo make this more performant for large lists!
42
+ * @param entry
43
+ */
44
+ push(entry: LogEntry) {
45
+ this.entries.push(entry)
46
+ this.dirty()
47
+ }
48
+
49
+ success(message: string, prefix: string | undefined) {
50
+ this.push({message, prefix, level: "success"})
51
+ }
52
+
53
+ info(message: string, prefix?: string) {
54
+ this.push({message, prefix, level: "info"})
55
+ }
56
+
57
+ warn(message: string, prefix?: string) {
58
+ this.push({message, prefix, level: "warn"})
59
+ }
60
+
61
+ error(message: string, prefix?: string) {
62
+ this.push({message, prefix, level: "error"})
63
+ }
64
+
65
+ debug(message: string, prefix?: string) {
66
+ this.push({message, prefix, level: "debug"})
67
+ }
68
+
69
+ renderEntry(parent: PartTag, entry: LogEntry) {
70
+ parent.div(`.log-entry.${entry.level}`, row => {
71
+ row.div('.message').text(entry.message)
72
+ })
73
+ }
74
+
75
+ }
package/terrier/modals.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import {Logger} from "tuff-core/logging"
2
- import {untypedKey} from "tuff-core/messages"
3
2
  import TerrierPart from "./parts/terrier-part"
4
3
  import {PartConstructor, PartTag} from "tuff-core/parts"
5
4
  import ContentPart from "./parts/content-part"
5
+ import Messages from "tuff-core/messages"
6
6
 
7
7
  const log = new Logger('Modals')
8
8
 
@@ -99,7 +99,7 @@ export abstract class ModalPart<TState> extends ContentPart<TState> {
99
99
  /**
100
100
  * Emit this key from anywhere inside a modal to pop it off the stack.
101
101
  */
102
- export const modalPopKey = untypedKey()
102
+ export const modalPopKey = Messages.untypedKey()
103
103
 
104
104
  export class ModalStackPart extends TerrierPart<{}> {
105
105
 
@@ -1,7 +1,7 @@
1
1
  import {Part, PartTag, StatelessPart, NoState, PartConstructor} from "tuff-core/parts"
2
- import { Size, Box, Side } from "tuff-core/box"
2
+ import { Size, Box, Side } from "tuff-core/boxes"
3
3
  import { Logger } from "tuff-core/logging"
4
- import {arrays} from "tuff-core";
4
+ import Arrays from "tuff-core/arrays"
5
5
 
6
6
  const log = new Logger('Overlays')
7
7
 
@@ -73,7 +73,7 @@ export class OverlayPart extends Part<NoState> {
73
73
  for (let i = this.layerStates.length-1; i>=0; i--) {
74
74
  if (this.layerStates[i].type == type) {
75
75
  const part = oldParts[i]
76
- this.layerStates = arrays.without(this.layerStates, this.layerStates[i])
76
+ this.layerStates = Arrays.without(this.layerStates, this.layerStates[i])
77
77
  this.updateLayers()
78
78
  return part
79
79
  }
@@ -96,7 +96,7 @@ export class OverlayPart extends Part<NoState> {
96
96
  removeLayer<StateType>(state: StateType): boolean {
97
97
  for (const layerState of this.layerStates) {
98
98
  if (layerState.partState == state) {
99
- this.layerStates = arrays.without(this.layerStates, layerState)
99
+ this.layerStates = Arrays.without(this.layerStates, layerState)
100
100
  this.updateLayers()
101
101
  return true
102
102
  }
@@ -164,15 +164,24 @@ type AnchorResult = {
164
164
  valid: boolean
165
165
  }
166
166
 
167
- function clampHorizontal(result: AnchorResult, size: Size, container: Size) {
167
+ function clampAnchorResult(result: AnchorResult, size: Size, container: Size) {
168
168
  if (container.width < size.width) {
169
169
  result.width = container.width
170
170
  }
171
171
  const rightOffset = container.width - (result.width ?? size.width) // don't hang off the right side of the viewport
172
- result.left = Math.max(0, Math.min(result.left, rightOffset)) // don't hang off the left side of the viewport
172
+ result.left = Math.max(0, Math.min(result.left, rightOffset))
173
173
  if (result.top < 0 || result.top+size.height > container.height) {
174
174
  result.valid = false
175
175
  }
176
+
177
+ if (container.height < size.height) {
178
+ result.height = container.height
179
+ }
180
+ const bottomOffset = container.height - (result.height ?? size.height) // don't hang off the bottom of the viewport
181
+ result.top = Math.max(0, Math.min(result.top, bottomOffset))
182
+ if (result.left < 0 || result.left + size.width > container.width) {
183
+ result.valid = false
184
+ }
176
185
  }
177
186
 
178
187
  function anchorBoxBottom(size: Size, anchor: Box, container: Size): AnchorResult {
@@ -181,7 +190,7 @@ function anchorBoxBottom(size: Size, anchor: Box, container: Size): AnchorResult
181
190
  left: anchor.x + anchor.width / 2 - size.width / 2,
182
191
  valid: true
183
192
  }
184
- clampHorizontal(result, size, container)
193
+ clampAnchorResult(result, size, container)
185
194
  return result
186
195
  }
187
196
 
@@ -191,28 +200,17 @@ function anchorBoxTop(size: Size, anchor: Box, container: Size): AnchorResult {
191
200
  left: anchor.x + anchor.width / 2 - size.width / 2,
192
201
  valid: true
193
202
  }
194
- clampHorizontal(result, size, container)
203
+ clampAnchorResult(result, size, container)
195
204
  return result
196
205
  }
197
206
 
198
- function clampVertical(result: AnchorResult, size: Size, container: Size) {
199
- if (container.height < size.height) {
200
- result.height = container.height
201
- }
202
- const bottomOffset = container.height - (result.height ?? size.height) // don't hang off the bottom of the viewport
203
- result.top = Math.max(0, Math.min(result.top, bottomOffset)) // don't hang off the top of the viewport
204
- if (result.left < 0 || result.left + size.width > container.width) {
205
- result.valid = false
206
- }
207
- }
208
-
209
207
  function anchorBoxLeft(size: Size, anchor: Box, container: Size): AnchorResult {
210
208
  const result = {
211
209
  top: anchor.y + anchor.height/2 - size.height / 2,
212
210
  left: anchor.x - size.width,
213
211
  valid: true
214
212
  }
215
- clampVertical(result, size, container)
213
+ clampAnchorResult(result, size, container)
216
214
  return result
217
215
  }
218
216
 
@@ -222,7 +220,7 @@ function anchorBoxRight(size: Size, anchor: Box, container: Size): AnchorResult
222
220
  left: anchor.x + anchor.width,
223
221
  valid: true
224
222
  }
225
- clampVertical(result, size, container)
223
+ clampAnchorResult(result, size, container)
226
224
  return result
227
225
  }
228
226
 
@@ -1,12 +1,13 @@
1
1
  import {ModalPart} from "../modals"
2
2
  import {Part, PartTag} from "tuff-core/parts"
3
- import {messages, logging} from "tuff-core"
4
3
  import Glyps from "../glyps"
4
+ import {Logger} from "tuff-core/logging"
5
+ import Messages from "tuff-core/messages"
5
6
 
6
- const log = new logging.Logger("GlypPicker")
7
+ const log = new Logger("GlypPicker")
7
8
 
8
9
 
9
- const pickKey = messages.typedKey<{ icon: string }>()
10
+ const pickKey = Messages.typedKey<{ icon: string }>()
10
11
 
11
12
  export type GlypPickerState = {
12
13
  icon?: string
@@ -20,7 +21,7 @@ class GlypPickerModal extends ModalPart<GlypPickerState> {
20
21
 
21
22
  grid!: GlypGrid
22
23
 
23
- filterKey = messages.untypedKey()
24
+ filterKey = Messages.untypedKey()
24
25
 
25
26
  async init() {
26
27
  await super.init()
@@ -1,7 +1,7 @@
1
1
  import ContentPart from "./content-part"
2
2
  import {PartTag} from "tuff-core/parts"
3
3
  import Fragments from "../fragments"
4
- import {untypedKey} from "tuff-core/messages";
4
+ import Messages from "tuff-core/messages"
5
5
 
6
6
  export type CollapsibleConfig = {
7
7
  collapsed?: boolean
@@ -13,8 +13,8 @@ export type CollapsibleConfig = {
13
13
  export default abstract class PanelPart<TState> extends ContentPart<TState & { collapsible?: CollapsibleConfig}> {
14
14
  protected static readonly DEFAULT_CHEVRON_SIDE: 'left' | 'right' = 'left'
15
15
 
16
- private _toggleCollapseKey = untypedKey()
17
- private _transitionEndKey = untypedKey()
16
+ private _toggleCollapseKey = Messages.untypedKey()
17
+ private _transitionEndKey = Messages.untypedKey()
18
18
 
19
19
  private _prevCollapsedState?: boolean
20
20
 
@@ -14,6 +14,16 @@ export class ProgressBarPart extends TerrierPart<ProgressState> {
14
14
  progress = 0
15
15
  color: ColorName = 'primary'
16
16
 
17
+ /**
18
+ * Set the total number of steps.
19
+ * @param total
20
+ */
21
+ setTotal(total: number) {
22
+ this.state.total = total
23
+ this.progress = Math.min(this.progress, this.state.total)
24
+ this.dirty()
25
+ }
26
+
17
27
  /**
18
28
  * Sets the progress and (optionally) color, then dirties the part.
19
29
  * @param progress
package/terrier/sheets.ts CHANGED
@@ -2,8 +2,8 @@ import {Action, IconName} from "./theme"
2
2
  import TerrierPart from "./parts/terrier-part"
3
3
  import {PartTag} from "tuff-core/parts"
4
4
  import Fragments from "./fragments"
5
- import {messages} from "tuff-core"
6
5
  import {Logger} from "tuff-core/logging"
6
+ import Messages from "tuff-core/messages"
7
7
 
8
8
  const log = new Logger('Sheets')
9
9
 
@@ -20,7 +20,7 @@ export type SheetState = {
20
20
  secondaryActions?: Action[]
21
21
  }
22
22
 
23
- const clearKey = messages.untypedKey()
23
+ const clearKey = Messages.untypedKey()
24
24
 
25
25
  export class Sheet<TState extends SheetState> extends TerrierPart<TState> {
26
26
 
package/terrier/tabs.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import {Logger} from "tuff-core/logging"
2
- import {typedKey} from "tuff-core/messages"
2
+ import Messages from "tuff-core/messages"
3
3
  import {Part, PartParent, PartTag, StatelessPart} from "tuff-core/parts"
4
4
  import TerrierPart from "./parts/terrier-part"
5
5
  import {Action, IconName, Packet} from "./theme"
@@ -37,8 +37,8 @@ export type TabContainerState = {
37
37
  export class TabContainerPart extends TerrierPart<TabContainerState> {
38
38
 
39
39
  private tabs = {} as Record<string, TabDefinition>
40
- changeTabKey = typedKey<{ tabKey: string }>()
41
- changeSideKey = typedKey<{ side: TabSide }>()
40
+ changeTabKey = Messages.typedKey<{ tabKey: string }>()
41
+ changeSideKey = Messages.typedKey<{ side: TabSide }>()
42
42
 
43
43
  async init() {
44
44
  this.onClick(this.changeTabKey, m => {
package/terrier/theme.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import {PartTag} from "tuff-core/parts"
2
- import {messages} from "tuff-core"
3
2
  import {GlypName} from "./glyps"
4
3
  import HubIcons, {HubIconName} from "./gen/hub-icons"
4
+ import {Key} from "tuff-core/messages"
5
5
 
6
6
  export interface ThemeType {
7
7
  readonly icons: string
@@ -22,7 +22,7 @@ export type ColorName = typeof ColorNames[number]
22
22
  * A combination of a message key and its associated data.
23
23
  */
24
24
  export type Packet = {
25
- key: messages.Key
25
+ key: Key
26
26
  data?: Record<string, unknown>
27
27
  }
28
28