terrier-engine 4.23.2 → 4.24.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.
@@ -0,0 +1,57 @@
1
+ import {PartTag} from "tuff-core/parts"
2
+ import DiveEditor, {DiveEditorState} from "./dive-editor"
3
+ import TerrierPart from "../../terrier/parts/terrier-part"
4
+ import {RegularSchedule, RegularScheduleForm} from "../../terrier/schedules"
5
+ import {Logger} from "tuff-core/logging"
6
+ import {EmailListForm} from "../../terrier/emails"
7
+
8
+ const log = new Logger("Dive Delivery")
9
+
10
+ export type DiveDeliverySettings = {
11
+ delivery_schedule: RegularSchedule
12
+ delivery_recipients: string[]
13
+ }
14
+
15
+ export class DiveDeliveryForm extends TerrierPart<DiveEditorState> {
16
+
17
+ scheduleForm!: RegularScheduleForm
18
+ recipientsForm!: EmailListForm
19
+
20
+ async init() {
21
+ const schedule = this.state.dive.delivery_schedule || {schedule_type: 'none'}
22
+ this.scheduleForm = this.makePart(RegularScheduleForm, schedule)
23
+ this.recipientsForm = this.makePart(EmailListForm, {emails: this.state.dive.delivery_recipients || []})
24
+
25
+ this.listen('datachanged', this.scheduleForm.dataChangedKey, m => {
26
+ log.info(`Schedule form data changed`, m.data)
27
+ this.emitMessage(DiveEditor.diveChangedKey, {})
28
+ })
29
+ this.listenMessage(this.recipientsForm.changedKey, m => {
30
+ log.info(`Recipients form data changed`, m.data)
31
+ this.emitMessage(DiveEditor.diveChangedKey, {})
32
+ })
33
+ }
34
+
35
+
36
+ get parentClasses(): Array<string> {
37
+ return ['tt-flex', 'column', 'gap', 'dd-dive-tool', 'tt-typography']
38
+ }
39
+
40
+ render(parent: PartTag): any {
41
+ parent.h3(".glyp-setup").text("Schedule")
42
+ parent.part(this.scheduleForm)
43
+ parent.h3(".glyp-users").text("Recipients")
44
+ parent.part(this.recipientsForm)
45
+ }
46
+
47
+ /**
48
+ * Serializes just the fields needed for the delivery settings.
49
+ */
50
+ async serialize(): Promise<DiveDeliverySettings> {
51
+ const delivery_schedule = await this.scheduleForm.serializeConcrete()
52
+ log.info(`Serialized ${delivery_schedule.schedule_type} delivery schedule`, delivery_schedule)
53
+ const delivery_recipients = this.recipientsForm.state.emails
54
+ return {delivery_schedule, delivery_recipients}
55
+ }
56
+
57
+ }
@@ -19,6 +19,8 @@ import Messages from "tuff-core/messages"
19
19
  import Arrays from "tuff-core/arrays"
20
20
  import {FormFields} from "tuff-core/forms"
21
21
  import Fragments from "../../terrier/fragments"
22
+ import {DiveDeliveryForm} from "./dive-delivery"
23
+ import {DivePlotsForm} from "./dive-plots"
22
24
 
23
25
  const log = new Logger("DiveEditor")
24
26
 
@@ -35,7 +37,12 @@ export type DiveEditorState = {
35
37
 
36
38
  export default class DiveEditor extends ContentPart<DiveEditorState> {
37
39
 
38
- tabs!: TabContainerPart
40
+ queryTabs!: TabContainerPart
41
+ settingsTabs!: TabContainerPart
42
+
43
+ deliveryForm!: DiveDeliveryForm
44
+
45
+ plotsForm!: DivePlotsForm
39
46
 
40
47
  newQueryKey = Messages.untypedKey()
41
48
  duplicateQueryKey = Messages.untypedKey()
@@ -45,25 +52,26 @@ export default class DiveEditor extends ContentPart<DiveEditorState> {
45
52
  queries = new Array<Query>()
46
53
 
47
54
  async init() {
48
- this.tabs = this.makePart(TabContainerPart, {side: 'top'})
55
+ this.queryTabs = this.makePart(TabContainerPart, {side: 'top'})
56
+ this.settingsTabs = this.makePart(TabContainerPart, {side: 'top'})
49
57
 
50
- this.tabs.addBeforeAction({
58
+ this.queryTabs.addBeforeAction({
51
59
  title: 'Queries:',
52
60
  icon: 'glyp-data_dive_query'
53
61
  })
54
- this.tabs.addAfterAction({
62
+ this.queryTabs.addAfterAction({
55
63
  title: "Add Another Query",
56
64
  classes: ['dd-hint', 'arrow-right', 'glyp-hint'],
57
65
  tooltip: "Each query represents a separate tab in the resulting spreadsheet",
58
66
  click: {key: this.newQueryKey}
59
67
  })
60
- this.tabs.addAfterAction({
68
+ this.queryTabs.addAfterAction({
61
69
  icon: 'glyp-copy',
62
70
  classes: ['duplicate-query'],
63
71
  tooltip: "Duplicate this query",
64
72
  click: {key: this.duplicateQueryKey}
65
73
  })
66
- this.tabs.addAfterAction({
74
+ this.queryTabs.addAfterAction({
67
75
  icon: 'glyp-plus_outline',
68
76
  classes: ['new-query'],
69
77
  tooltip: "Add a new query to this Dive",
@@ -78,7 +86,7 @@ export default class DiveEditor extends ContentPart<DiveEditorState> {
78
86
  this.listenMessage(QueryForm.settingsChangedKey, m => {
79
87
  const query = m.data
80
88
  log.info(`Query settings changed`, query)
81
- this.tabs.updateTab({key: query.id, title: query.name})
89
+ this.queryTabs.updateTab({key: query.id, title: query.name})
82
90
  })
83
91
 
84
92
  this.onClick(this.newQueryKey, _ => {
@@ -86,7 +94,7 @@ export default class DiveEditor extends ContentPart<DiveEditorState> {
86
94
  })
87
95
 
88
96
  this.onClick(this.duplicateQueryKey, _ => {
89
- const id = this.tabs.currentTagKey
97
+ const id = this.queryTabs.currentTagKey
90
98
  if (id?.length) {
91
99
  const query = this.queries.find(q => q.id == id)
92
100
  if (query) {
@@ -110,6 +118,10 @@ export default class DiveEditor extends ContentPart<DiveEditorState> {
110
118
  this.deleteQuery(m.data.id)
111
119
  })
112
120
  })
121
+
122
+ this.deliveryForm = this.settingsTabs.upsertTab({key: 'delivery', title: "Delivery", icon: "glyp-email"}, DiveDeliveryForm, this.state)
123
+
124
+ this.plotsForm = this.settingsTabs.upsertTab({key: 'plots', title: "Plots", icon: "glyp-differential"}, DivePlotsForm, this.state)
113
125
  }
114
126
 
115
127
  /**
@@ -127,30 +139,43 @@ export default class DiveEditor extends ContentPart<DiveEditorState> {
127
139
  */
128
140
  private addQueryTab(query: Query) {
129
141
  const state = {...this.state, query}
130
- this.tabs.upsertTab({key: query.id, title: query.name}, QueryEditor, state)
142
+ this.queryTabs.upsertTab({key: query.id, title: query.name}, QueryEditor, state)
131
143
  }
132
144
 
133
145
  deleteQuery(id: string) {
134
146
  log.info(`Deleting query ${id}`)
135
147
  if (Arrays.deleteIf(this.queries, q => q.id == id) > 0) {
136
- this.tabs.removeTab(id)
148
+ this.queryTabs.removeTab(id)
137
149
  this.dirty()
138
150
  }
139
151
  }
140
152
 
141
153
  get parentClasses(): Array<string> {
142
- return ['dd-dive-editor']
154
+ return ['dd-dive-editor', 'tt-flex']
143
155
  }
144
156
 
145
157
  renderContent(parent: PartTag) {
146
- parent.part(this.tabs)
158
+ parent.div(".stretch", col => {
159
+ col.part(this.queryTabs)
160
+ })
161
+ parent.div(".shrink", col => {
162
+ col.part(this.settingsTabs)
163
+ })
147
164
  }
148
165
 
149
166
  static readonly deleteQueryKey = Messages.typedKey<{ id: string }>()
150
167
 
151
168
  async serialize(): Promise<DdDive> {
152
169
  const queries = this.queries
153
- return {...this.state.dive, query_data: {queries}}
170
+
171
+ const deliverySettings = await this.deliveryForm.serialize()
172
+
173
+ return {
174
+ ...this.state.dive,
175
+ delivery_schedule: deliverySettings.delivery_schedule,
176
+ delivery_recipients: deliverySettings.delivery_recipients,
177
+ query_data: {queries}
178
+ }
154
179
  }
155
180
 
156
181
  }
@@ -334,7 +359,7 @@ class NewQueryModal extends ModalPart<NewQueryState> {
334
359
  }
335
360
  const query = {...settings, id: Ids.makeUuid(), from: {model: model.name}}
336
361
  this.state.editor.addQuery(query)
337
- this.state.editor.tabs.showTab(query.id)
362
+ this.state.editor.queryTabs.showTab(query.id)
338
363
  this.pop()
339
364
  }
340
365
 
@@ -379,7 +404,7 @@ class DuplicateQueryModal extends ModalPart<DuplicateQueryState> {
379
404
  const newName = this.fields.data.name
380
405
  const query = {...Queries.duplicate(this.state.query), name: newName}
381
406
  this.state.editor.addQuery(query)
382
- this.state.editor.tabs.showTab(query.id)
407
+ this.state.editor.queryTabs.showTab(query.id)
383
408
  this.pop()
384
409
  this.app.successToast("Duplicated Query", 'glyp-copy')
385
410
  }
@@ -0,0 +1,11 @@
1
+ import TerrierFormPart from "../../terrier/parts/terrier-form-part"
2
+ import {DiveEditorState} from "./dive-editor"
3
+ import {PartTag} from "tuff-core/parts"
4
+
5
+
6
+ export class DivePlotsForm extends TerrierFormPart<DiveEditorState> {
7
+ render(parent: PartTag): any {
8
+ parent.h3(".coming-soon.glyp-developer").text("Coming Soon")
9
+ }
10
+
11
+ }
@@ -1,4 +1,4 @@
1
- // This file was automatically generated, DO NOT EDIT IT MANUALLY!
1
+ // This file was automatically generated on 2024-05-16 13:17:01 -0500, DO NOT EDIT IT MANUALLY!
2
2
 
3
3
  import { Query } from "../queries/queries"
4
4
 
@@ -6,10 +6,12 @@ import { DdUser } from "../dd-user"
6
6
 
7
7
  import { FilterInput } from "../queries/filters"
8
8
 
9
- import { OptionalProps } from "tuff-core/types"
10
-
11
9
  import { Attachment } from "../../terrier/attachments"
12
10
 
11
+ import { RegularSchedule } from "../../terrier/schedules"
12
+
13
+ import { OptionalProps } from "tuff-core/types"
14
+
13
15
  export type DdDive = {
14
16
  id: string
15
17
  created_at: string
@@ -29,6 +31,8 @@ export type DdDive = {
29
31
  sort_order?: number
30
32
  query_data?: { queries: Query[] }
31
33
  dive_types: string[]
34
+ delivery_recipients?: string[]
35
+ delivery_schedule?: RegularSchedule
32
36
  created_by?: DdUser
33
37
  updated_by?: DdUser
34
38
  dd_dive_group?: DdDiveGroup
@@ -55,6 +59,8 @@ export type UnpersistedDdDive = {
55
59
  sort_order?: number
56
60
  query_data?: { queries: Query[] }
57
61
  dive_types: string[]
62
+ delivery_recipients?: string[]
63
+ delivery_schedule?: RegularSchedule
58
64
  created_by?: DdUser
59
65
  updated_by?: DdUser
60
66
  dd_dive_group?: DdDiveGroup
@@ -123,6 +129,8 @@ export type DdDiveRun = {
123
129
  output_data?: object
124
130
  output_file_data?: Attachment | { path: string }
125
131
  status: "initial" | "running" | "success" | "error"
132
+ delivery_recipients?: string[]
133
+ delivery_data?: object
126
134
  created_by?: DdUser
127
135
  updated_by?: DdUser
128
136
  dd_dive?: DdDive
@@ -144,6 +152,8 @@ export type UnpersistedDdDiveRun = {
144
152
  output_data?: object
145
153
  output_file_data?: Attachment | { path: string }
146
154
  status: "initial" | "running" | "success" | "error"
155
+ delivery_recipients?: string[]
156
+ delivery_data?: object
147
157
  created_by?: DdUser
148
158
  updated_by?: DdUser
149
159
  dd_dive?: DdDive
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "files": [
5
5
  "*"
6
6
  ],
7
- "version": "4.23.2",
7
+ "version": "4.24.0",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "https://github.com/Terrier-Tech/terrier-engine"
@@ -0,0 +1,129 @@
1
+ import {Logger} from "tuff-core/logging"
2
+ import {PartTag} from "tuff-core/parts"
3
+ import Messages from "tuff-core/messages"
4
+ import TerrierPart from "./parts/terrier-part"
5
+ import Arrays from "tuff-core/arrays"
6
+ import Tooltips from "./tooltips"
7
+
8
+ const log = new Logger("Emails")
9
+
10
+ ////////////////////////////////////////////////////////////////////////////////
11
+ // Validation
12
+ ////////////////////////////////////////////////////////////////////////////////
13
+
14
+ /**
15
+ * Use this to split a string with multiple e-mails.
16
+ */
17
+ const SplitRegex = /[\s,;]+/
18
+
19
+ /**
20
+ * Splits a string containing multiple e-mail addresses.
21
+ * @param emails
22
+ */
23
+ function split(emails: string): string[] {
24
+ return emails.split(SplitRegex)
25
+ }
26
+
27
+ /**
28
+ * Basic e-mail validation regex.
29
+ */
30
+ const ValidationRegex = /[^\s@]+@[^\s@]+\.[^\s;,@]+/
31
+
32
+ /**
33
+ * Performs basic validation on an e-mail address.
34
+ * @param email
35
+ */
36
+ function validate(email: string): boolean {
37
+ return ValidationRegex.test(email)
38
+ }
39
+
40
+
41
+ ////////////////////////////////////////////////////////////////////////////////
42
+ // Form
43
+ ////////////////////////////////////////////////////////////////////////////////
44
+
45
+ export type EmailListState = {
46
+ emails: string[]
47
+ }
48
+
49
+ /**
50
+ * An editor for an array of e-mail addresses.
51
+ * Updates the emails state as they're changed, no need to serialize.
52
+ * Listen for a `changedKey` message to know when they change.
53
+ */
54
+ export class EmailListForm extends TerrierPart<EmailListState> {
55
+
56
+ /**
57
+ * A message with this key will get emitted any time the addresses change.
58
+ */
59
+ changedKey = Messages.typedKey<EmailListState>()
60
+
61
+ private _changeKey = Messages.typedKey<{ index: number }>()
62
+
63
+ /**
64
+ * Gets temporarily set to to `true` when a change is made and it should grab the focus of the last field after rendering.
65
+ * @private
66
+ */
67
+ private focusLastField = false
68
+
69
+ async init() {
70
+ this.onChange(this._changeKey, m => {
71
+ const i = m.data.index
72
+ const v = m.value
73
+ let emails: Array<string|null> = this.state.emails
74
+ log.info(`E-Mail address ${i} changed to ${v}`, m)
75
+ if (i >= emails.length) {
76
+ // it's a new value
77
+ emails.push(v)
78
+ }
79
+ else {
80
+ // it's an existing value
81
+ emails[i] = v
82
+ }
83
+ this.state.emails = Arrays.compactStrings(emails)
84
+ Tooltips.clear()
85
+ this.focusLastField = true
86
+ this.dirty()
87
+ this.emitMessage(this.changedKey, this.state)
88
+ })
89
+ }
90
+
91
+ get parentClasses(): Array<string> {
92
+ return ['tt-flex', 'column', 'gap', 'email-list-form', 'tt-form']
93
+ }
94
+
95
+ render(parent: PartTag): any {
96
+ const emails = this.state.emails.concat(['']) // always make sure there's one more field
97
+ emails.forEach((email, index) => {
98
+ const field = parent.input({type: 'email', value: email, placeholder: "bob@example.com"})
99
+ .emitChange(this._changeKey, {index})
100
+ if (email.length && !Emails.validate(email)) {
101
+ field.class("error").data({tooltip: `'${email}' is not a valid e-mail address`})
102
+ }
103
+ })
104
+ }
105
+
106
+
107
+ update(elem: HTMLElement) {
108
+ super.update(elem)
109
+
110
+ if (this.focusLastField) {
111
+ this.focusLastField = false
112
+ const fields = elem.querySelectorAll("input[type=email]")
113
+ if (fields.length) {
114
+ (fields.item(fields.length - 1) as HTMLElement).focus()
115
+ }
116
+ }
117
+ }
118
+ }
119
+
120
+
121
+ ////////////////////////////////////////////////////////////////////////////////
122
+ // Export
123
+ ////////////////////////////////////////////////////////////////////////////////
124
+
125
+ const Emails = {
126
+ split,
127
+ validate
128
+ }
129
+ export default Emails
package/terrier/glyps.ts CHANGED
@@ -1,8 +1,8 @@
1
- // This file was automatically generated by glyps:compile on 05/14/24 10:35 AM, DO NOT MANUALLY EDIT!
1
+ // This file was automatically generated by glyps:compile on 05/16/24 4:04 PM, DO NOT MANUALLY EDIT!
2
2
 
3
3
  import Strings from "tuff-core/strings"
4
4
 
5
- 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-fogging', '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-icon', 'glyp-identifier', 'glyp-import', 'glyp-import_data', '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-job_plan', 'glyp-job_plan_history', 'glyp-job_plan_run', '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_import', 'glyp-location_kind', 'glyp-location_message', 'glyp-location_origin', 'glyp-location_seed', '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-multi_tech_order', '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-threshold', '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
5
+ 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-fogging', '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-icon', 'glyp-identifier', 'glyp-import', 'glyp-import_data', '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-job_plan', 'glyp-job_plan_history', 'glyp-job_plan_run', '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_import', 'glyp-location_kind', 'glyp-location_message', 'glyp-location_origin', 'glyp-location_seed', '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-multi_tech_order', '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_digest', '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-threshold', '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
6
 
7
7
  /**
8
8
  * Format a glyp name so that it's suitable to show to the user.
@@ -0,0 +1,179 @@
1
+ import TerrierFormPart from "./parts/terrier-form-part"
2
+ import {PartTag} from "tuff-core/parts"
3
+ import * as inflection from "inflection"
4
+ import Messages from "tuff-core/messages"
5
+ import {Logger} from "tuff-core/logging"
6
+ import dayjs from "dayjs"
7
+
8
+ const log = new Logger("Schedules")
9
+
10
+
11
+ ////////////////////////////////////////////////////////////////////////////////
12
+ // Types
13
+ ////////////////////////////////////////////////////////////////////////////////
14
+
15
+
16
+ export type EmptySchedule = {
17
+ schedule_type: 'none'
18
+ }
19
+
20
+ const HoursOfDay = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23'] as const
21
+
22
+ export type HourOfDay = typeof HoursOfDay[number]
23
+
24
+ const HourOfDayOptions = HoursOfDay.map(h => {
25
+ return {value: h.toString(), title: dayjs().hour(parseInt(h)).format('h A')}
26
+ })
27
+
28
+ type BaseSchedule = {
29
+ hour_of_day: HourOfDay
30
+ }
31
+
32
+ const DaysOfWeek = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'] as const
33
+
34
+ export type DayOfWeek = typeof DaysOfWeek[number]
35
+
36
+ const DayOfWeekOptions = DaysOfWeek.map(d => {
37
+ return {value: d.toString(), title: inflection.capitalize(d)}
38
+ })
39
+
40
+ export type DailySchedule = BaseSchedule & {
41
+ schedule_type: 'daily'
42
+ }
43
+
44
+ export type WeeklySchedule = BaseSchedule & {
45
+ schedule_type: 'weekly'
46
+ day_of_week: DayOfWeek
47
+ }
48
+
49
+ const DaysOfMonth = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28'] as const
50
+
51
+ export type DayOfMonth = typeof DaysOfMonth[number]
52
+
53
+ const DayOfMonthOptions = DaysOfMonth.map(d => {
54
+ return {value: d, title: inflection.ordinalize(d)}
55
+ })
56
+
57
+ export type MonthlySchedule = BaseSchedule & {
58
+ schedule_type: 'monthly'
59
+ day_of_month: DayOfMonth
60
+ }
61
+
62
+ /**
63
+ * A schedule for something that happens on a regular daily/weekly/monthly basis.
64
+ */
65
+ export type RegularSchedule = EmptySchedule | DailySchedule | WeeklySchedule | MonthlySchedule
66
+
67
+ export type ScheduleType = 'none' | 'daily' | 'weekly' | 'monthly'
68
+
69
+ /**
70
+ * All possible variations of RegularSchedule.
71
+ */
72
+ export type CombinedRegularSchedule = {
73
+ schedule_type: ScheduleType
74
+ hour_of_day?: HourOfDay
75
+ day_of_week?: DayOfWeek
76
+ day_of_month?: DayOfMonth
77
+ }
78
+
79
+
80
+ ////////////////////////////////////////////////////////////////////////////////
81
+ // Form
82
+ ////////////////////////////////////////////////////////////////////////////////
83
+
84
+ export class RegularScheduleForm extends TerrierFormPart<CombinedRegularSchedule> {
85
+
86
+ scheduleTypeChangeKey = Messages.typedKey<{schedule_type: ScheduleType}>()
87
+
88
+ async init() {
89
+ this.onChange(this.scheduleTypeChangeKey, m => {
90
+ log.info(`Schedule type changed to ${m.data.schedule_type}`)
91
+ this.dirty()
92
+ })
93
+ }
94
+
95
+ get parentClasses(): Array<string> {
96
+ return ['tt-flex', 'column', 'gap', 'regular-schedule-form', 'tt-form']
97
+ }
98
+
99
+
100
+ render(parent: PartTag): any {
101
+ parent.label('.caption-size', label => {
102
+ this.radio(label, 'schedule_type', 'none')
103
+ .emitChange(this.scheduleTypeChangeKey, {schedule_type: 'none'})
104
+ label.span().text("Do Not Deliver")
105
+ })
106
+
107
+ parent.label('.caption-size', label => {
108
+ this.radio(label, 'schedule_type', 'daily')
109
+ .emitChange(this.scheduleTypeChangeKey, {schedule_type: 'daily'})
110
+ label.span().text("Deliver Daily")
111
+ })
112
+ if (this.state.schedule_type == 'daily') {
113
+ parent.div('.schedule-type-fields.daily.tt-flex.gap', row => {
114
+ this.select(row, 'hour_of_day', HourOfDayOptions)
115
+ })
116
+ }
117
+
118
+ parent.label('.caption-size', label => {
119
+ this.radio(label, 'schedule_type', 'weekly')
120
+ .emitChange(this.scheduleTypeChangeKey, {schedule_type: 'weekly'})
121
+ label.span().text("Deliver Weekly")
122
+ })
123
+ if (this.state.schedule_type == 'weekly') {
124
+ parent.div('.schedule-type-fields.weekly.tt-flex.gap', row => {
125
+ this.select(row, 'day_of_week', DayOfWeekOptions)
126
+ .data({tooltip: "Day of the week"})
127
+ this.select(row, 'hour_of_day', HourOfDayOptions)
128
+ })
129
+ }
130
+
131
+ parent.label('.caption-size', label => {
132
+ this.radio(label, 'schedule_type', 'monthly')
133
+ .emitChange(this.scheduleTypeChangeKey, {schedule_type: 'monthly'})
134
+ label.span().text("Deliver Monthly")
135
+ })
136
+ if (this.state.schedule_type == 'monthly') {
137
+ parent.div('.schedule-type-fields.monthly.tt-flex.gap', row => {
138
+ this.select(row, 'day_of_month', DayOfMonthOptions)
139
+ .data({tooltip: "Day of the month"})
140
+ this.select(row, 'hour_of_day', HourOfDayOptions)
141
+ })
142
+ }
143
+ }
144
+
145
+
146
+ /**
147
+ * Serializes the form into a RegularSchedule based on the selected schedule type.
148
+ */
149
+ async serializeConcrete(): Promise<RegularSchedule> {
150
+ const raw = await this.serialize()
151
+ const schedule_type = raw.schedule_type
152
+ const hour_of_day = raw.hour_of_day || '0'
153
+ switch (schedule_type) {
154
+ case 'none':
155
+ return {schedule_type}
156
+ case 'daily':
157
+ return {schedule_type, hour_of_day}
158
+ case 'weekly':
159
+ return {schedule_type, hour_of_day, day_of_week: raw.day_of_week || 'sunday'}
160
+ case 'monthly':
161
+ return {schedule_type, hour_of_day, day_of_month: raw.day_of_month || '1'}
162
+ default:
163
+ throw `Invalid schedule type: ${schedule_type}`
164
+ }
165
+ }
166
+
167
+ }
168
+
169
+ ////////////////////////////////////////////////////////////////////////////////
170
+ // Export
171
+ ////////////////////////////////////////////////////////////////////////////////
172
+
173
+ const Schedules = {
174
+ DaysOfWeek,
175
+ DaysOfMonth,
176
+ HoursOfDay
177
+ }
178
+ export default Schedules
179
+
@@ -17,6 +17,15 @@ function ensureContainer(): HTMLElement {
17
17
  return container
18
18
  }
19
19
 
20
+ /**
21
+ * Hides the current tooltip.
22
+ */
23
+ function clear() {
24
+ if (container) {
25
+ container.classList.remove('show')
26
+ }
27
+ }
28
+
20
29
  function onEnter(target: HTMLElement) {
21
30
  log.debug("Mouse enter", target)
22
31
  const container = ensureContainer()
@@ -27,9 +36,7 @@ function onEnter(target: HTMLElement) {
27
36
 
28
37
  function onLeave(target: HTMLElement) {
29
38
  log.debug("Mouse leave", target)
30
- if (container) {
31
- container.classList.remove('show')
32
- }
39
+ clear()
33
40
  }
34
41
 
35
42
  /**
@@ -58,7 +65,8 @@ function init(root: HTMLElement) {
58
65
  }
59
66
 
60
67
  const Tooltips = {
61
- init
68
+ init,
69
+ clear
62
70
  }
63
71
 
64
72
  export default Tooltips