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.
- package/data-dive/dives/dive-delivery.ts +57 -0
- package/data-dive/dives/dive-editor.ts +40 -15
- package/data-dive/dives/dive-plots.ts +11 -0
- package/data-dive/gen/models.ts +13 -3
- package/package.json +1 -1
- package/terrier/emails.ts +129 -0
- package/terrier/glyps.ts +2 -2
- package/terrier/schedules.ts +179 -0
- package/terrier/tooltips.ts +12 -4
|
@@ -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
|
-
|
|
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.
|
|
55
|
+
this.queryTabs = this.makePart(TabContainerPart, {side: 'top'})
|
|
56
|
+
this.settingsTabs = this.makePart(TabContainerPart, {side: 'top'})
|
|
49
57
|
|
|
50
|
-
this.
|
|
58
|
+
this.queryTabs.addBeforeAction({
|
|
51
59
|
title: 'Queries:',
|
|
52
60
|
icon: 'glyp-data_dive_query'
|
|
53
61
|
})
|
|
54
|
-
this.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
+
}
|
package/data-dive/gen/models.ts
CHANGED
|
@@ -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
|
@@ -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/
|
|
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
|
+
|
package/terrier/tooltips.ts
CHANGED
|
@@ -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
|
-
|
|
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
|