terrier-engine 4.3.0 → 4.4.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/api.ts CHANGED
@@ -4,6 +4,11 @@ import { QueryParams } from "tuff-core/urls"
4
4
  const log = new Logger('Api')
5
5
  log.level = 'debug'
6
6
 
7
+
8
+ ////////////////////////////////////////////////////////////////////////////////
9
+ // Basic Requests
10
+ ////////////////////////////////////////////////////////////////////////////////
11
+
7
12
  /**
8
13
  * All API responses containing these fields.
9
14
  */
@@ -104,9 +109,123 @@ async function post<ResponseType>(url: string, body: Record<string, unknown> | F
104
109
  }
105
110
 
106
111
 
112
+ ////////////////////////////////////////////////////////////////////////////////
113
+ // Event Streams
114
+ ////////////////////////////////////////////////////////////////////////////////
115
+
116
+ type LogLevel = 'success' | 'info' | 'warn' | 'debug'
117
+
118
+ /**
119
+ * Type of log events from a streaming response.
120
+ */
121
+ export type LogEvent = {
122
+ level: LogLevel
123
+ prefix?: string
124
+ message: string
125
+ }
126
+
127
+ /**
128
+ * Type of error events from a streaming response.
129
+ */
130
+ export type ErrorEvent = {
131
+ prefix?: string
132
+ message: string
133
+ backtrace: string[]
134
+ }
135
+
136
+ /**
137
+ * Configure a `Streamer`.
138
+ */
139
+ export type StreamOptions = {
140
+ keepAlive?: boolean
141
+ }
142
+
143
+ type noArgListener = () => any
144
+
145
+ type StreamLifecycle = 'close'
146
+
147
+ /**
148
+ * Exposes a typesafe API for handling streaming responses using SSE.
149
+ */
150
+ export class Streamer {
151
+
152
+ sse!: EventSource
153
+ lifecycleListeners: Record<StreamLifecycle, noArgListener[]> = {
154
+ close: []
155
+ }
156
+
157
+ constructor(readonly url: string, readonly options: StreamOptions) {
158
+ this.sse = new EventSource(url)
159
+
160
+ // this is a special event sent by the ResponseStreamer on the server
161
+ // to tell us that the request is done
162
+ this.sse.addEventListener('_close', evt => {
163
+ if (!this.options.keepAlive) {
164
+ log.debug(`Closing Streamer at ${url}`, evt)
165
+ this.sse.close()
166
+ for (const listener of this.lifecycleListeners['close']) {
167
+ listener()
168
+ }
169
+ }
170
+ })
171
+ }
172
+
173
+ /**
174
+ * Register a listener for events of the given type.
175
+ * @param type
176
+ * @param listener
177
+ */
178
+ on<T>(type: string, listener: (event: T) => any) {
179
+ this.sse.addEventListener(type, event => {
180
+ const data = JSON.parse(event.data) as T
181
+ log.debug(`${type} event`, data)
182
+ listener(data)
183
+ })
184
+ return this
185
+ }
186
+
187
+ /**
188
+ * Listen specifically for log events.
189
+ * @param listener
190
+ */
191
+ onLog(listener: (event: LogEvent) => any) {
192
+ return this.on<LogEvent>('_log', listener)
193
+ }
194
+
195
+ /**
196
+ * Listen specifically for error events.
197
+ * @param listener
198
+ */
199
+ onError(listener: (event: ErrorEvent) => any) {
200
+ return this.on<ErrorEvent>('_error', listener)
201
+ }
202
+
203
+ onClose(listener: noArgListener) {
204
+ this.lifecycleListeners['close'].push(listener)
205
+ }
206
+ }
207
+
208
+
209
+
210
+ /**
211
+ * Creates a streaming response for the given endpoint.
212
+ * @param url
213
+ * @param options pass `keepAlive: true` to keep retrying the request
214
+ * @return a `Streamer` on which you attach event handlers
215
+ */
216
+ function stream(url: string, options: StreamOptions={}): Streamer {
217
+ return new Streamer(url, options)
218
+ }
219
+
220
+
221
+ ////////////////////////////////////////////////////////////////////////////////
222
+ // Export
223
+ ////////////////////////////////////////////////////////////////////////////////
224
+
107
225
  const Api = {
108
226
  safeGet,
109
227
  safePost,
110
- post
228
+ post,
229
+ stream
111
230
  }
112
231
  export default Api
package/app.ts CHANGED
@@ -9,6 +9,8 @@ import {OverlayLayerType, OverlayPart} from "./overlays"
9
9
 
10
10
  // @ts-ignore
11
11
  import logoUrl from './images/optimized/terrier-hub-logo-light.svg'
12
+ import Sheets, {AlertSheetState, ConfirmSheetState, Sheet, SheetState} from "./sheets";
13
+ import {messages} from "tuff-core";
12
14
 
13
15
  const log = new Logger('App')
14
16
  Logger.level = 'info'
@@ -93,4 +95,58 @@ export abstract class TerrierApp<TState> extends TerrierPart<TState> {
93
95
  }
94
96
 
95
97
 
98
+ /// Sheets
99
+
100
+ /**
101
+ * Shows a confirm sheet to the user, asking them a question
102
+ * @param options
103
+ * @param callback gets called if the user hits "Confirm"
104
+ */
105
+ confirm(options: ConfirmSheetState, callback: () => any) {
106
+ const key = messages.untypedKey()
107
+ const state = {...options,
108
+ primaryActions: [
109
+ {
110
+ title: 'Confirm',
111
+ icon: 'glyp-checkmark',
112
+ click: {key}
113
+ }
114
+ ],
115
+ secondaryActions: [
116
+ {
117
+ title: 'Cancel',
118
+ icon: 'glyp-close',
119
+ classes: ['secondary'],
120
+ click: {key: Sheets.clearKey}
121
+ }
122
+ ]
123
+ } as SheetState
124
+ const sheet = this.overlayPart.getOrCreateLayer(Sheet<SheetState>, state, 'sheet')
125
+ sheet.onClick(key, _ => {
126
+ sheet.clear()
127
+ callback()
128
+ })
129
+ sheet.dirty()
130
+ }
131
+
132
+ /**
133
+ * Shows an alert sheet to the user with a message (but no choices).
134
+ * @param options
135
+ */
136
+ alert(options: AlertSheetState) {
137
+ const state = {...options,
138
+ primaryActions: [
139
+ {
140
+ title: 'Okay',
141
+ icon: 'glyp-checkmark',
142
+ click: {key: Sheets.clearKey},
143
+ classes: ['secondary']
144
+ }
145
+ ]
146
+ } as SheetState
147
+ const sheet = this.overlayPart.getOrCreateLayer(Sheet<SheetState>, state, 'sheet')
148
+ sheet.dirty()
149
+ }
150
+
151
+
96
152
  }
package/attachments.ts ADDED
@@ -0,0 +1,55 @@
1
+ import {Logger} from "tuff-core/logging"
2
+
3
+ const log = new Logger('Attachments')
4
+
5
+ type MetaData = {
6
+ size: number
7
+ filename: string
8
+ mime_type: string
9
+ }
10
+
11
+ type ShrineAttachment = {
12
+ id: string
13
+ storage: string
14
+ metadata: MetaData
15
+ path?: string // used to send tmp filepath to server
16
+ }
17
+
18
+ type Derivatives = {
19
+ thumbnail?: ShrineAttachment
20
+ }
21
+
22
+ export type Attachment = ShrineAttachment & { derivatives?: Derivatives }
23
+
24
+ /**
25
+ * Constructs a url pointing to the original or a derivative of the attachment
26
+ * @param attachment
27
+ * @param derivative
28
+ */
29
+ function url(attachment: Attachment, derivative: keyof Derivatives | null) {
30
+ let path = '/uploads'
31
+ let id
32
+
33
+ if (derivative?.length && attachment.derivatives) {
34
+ const derivativeId = attachment.derivatives[derivative]?.id
35
+ if (derivativeId?.length) {
36
+ path += '/permanent'
37
+ id = derivativeId
38
+ } else {
39
+ log.info(`${derivative} not found, returning original`)
40
+ }
41
+ }
42
+
43
+ if (!id) {
44
+ path += '/cache'
45
+ id = attachment.id
46
+ }
47
+
48
+ return `${path}/${id}`
49
+ }
50
+
51
+ const Attachments = {
52
+ url
53
+ }
54
+
55
+ export default Attachments
package/format.ts ADDED
@@ -0,0 +1,24 @@
1
+
2
+ ////////////////////////////////////////////////////////////////////////////////
3
+ // Currency
4
+ ////////////////////////////////////////////////////////////////////////////////
5
+
6
+ /**
7
+ * Formats a cents value as a dollars string.
8
+ * @param c an integer number of cents
9
+ */
10
+ function cents(c: number | string): string {
11
+ const num = typeof c == 'number' ? c : parseInt(c)
12
+ return '$' + (num/100).toFixed(2)
13
+ }
14
+
15
+
16
+ ////////////////////////////////////////////////////////////////////////////////
17
+ // Export
18
+ ////////////////////////////////////////////////////////////////////////////////
19
+
20
+ const Format = {
21
+ cents
22
+ }
23
+
24
+ export default Format
package/forms.ts CHANGED
@@ -1,5 +1,10 @@
1
- import {SelectOptions} from "tuff-core/forms"
1
+ import {Field, FormFields, FormPartData, InputType, KeyOfType, SelectOptions} from "tuff-core/forms"
2
2
  import {strings} from "tuff-core"
3
+ import {DbErrors} from "./db-client"
4
+ import {PartTag} from "tuff-core/parts"
5
+ import {InputTag, InputTagAttrs} from "tuff-core/html"
6
+ import TerrierPart from "./parts/terrier-part";
7
+ import {an} from "vitest/dist/types-ad1c3f45";
3
8
 
4
9
  ////////////////////////////////////////////////////////////////////////////////
5
10
  // Options
@@ -20,6 +25,52 @@ function titleizeOptions(opts: string[], blank?: string): SelectOptions {
20
25
  }
21
26
 
22
27
 
28
+ ////////////////////////////////////////////////////////////////////////////////
29
+ // Form Fields
30
+ ////////////////////////////////////////////////////////////////////////////////
31
+
32
+ /**
33
+ * Override regular `FormFields` methods to include validation errors.
34
+ */
35
+ export class TerrierFormFields<T extends FormPartData> extends FormFields<T> {
36
+
37
+ errors?: DbErrors<T>
38
+
39
+ /**
40
+ * You must pass a `TerrierPart` so that we can render the error bubble with it.
41
+ * @param part
42
+ * @param data
43
+ * @param errors
44
+ */
45
+ constructor(part: TerrierPart<any>, data: T, errors?: DbErrors<T>) {
46
+ super(part, data)
47
+ this.errors = errors
48
+ }
49
+
50
+ protected input<Key extends KeyOfType<T, any> & string>(parent: PartTag, type: InputType, name: Key, serializerType: {
51
+ new(name: string): Field<any, Element>
52
+ }, attrs?: InputTagAttrs): InputTag {
53
+ if (this.errors && this.errors[name]) {
54
+ attrs ||= {}
55
+ attrs.classes ||= []
56
+ attrs.classes.push('error')
57
+ }
58
+ return super.input(parent, type, name, serializerType, attrs);
59
+ }
60
+
61
+ /**
62
+ * Only renders the error bubble if the errors are set.
63
+ * @param parent
64
+ */
65
+ renderErrorBubble(parent: PartTag) {
66
+ if (this.errors) {
67
+ (this.part as TerrierPart<an>).renderErrorBubble(parent, this.errors)
68
+ }
69
+ }
70
+ }
71
+
72
+
73
+
23
74
  ////////////////////////////////////////////////////////////////////////////////
24
75
  // Export
25
76
  ////////////////////////////////////////////////////////////////////////////////
package/fragments.ts CHANGED
@@ -11,6 +11,18 @@ abstract class ContentFragment {
11
11
 
12
12
  }
13
13
 
14
+
15
+ protected _classes: string[] = []
16
+
17
+ /**
18
+ * Add arbitrary classes to the top-level fragment.
19
+ * @param c
20
+ */
21
+ classes(...c: string[]) {
22
+ this._classes.push(...c)
23
+ return this
24
+ }
25
+
14
26
  protected _title?: string
15
27
 
16
28
  /**
@@ -93,7 +105,7 @@ export class PanelFragment extends ContentFragment {
93
105
  }
94
106
  })
95
107
  panelActions(panel, this.actions, this.theme)
96
- })
108
+ }).class(...this._classes)
97
109
  }
98
110
 
99
111
  }
package/glyps.ts CHANGED
@@ -1,6 +1,6 @@
1
- // This file was automatically generated by glyps:compile on 06/10/23 11:25 AM, DO NOT MANUALLY EDIT!
1
+ // This file was automatically generated by glyps:compile on 06/18/23 3:03 PM, DO NOT MANUALLY EDIT!
2
2
 
3
- 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-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-expiring', 'glyp-exterior', 'glyp-extras', 'glyp-facebook', 'glyp-farm', 'glyp-farm_grain_seed', 'glyp-fast_and_frequent', 'glyp-favorite', 'glyp-feedback', 'glyp-field_guide', 'glyp-field_training', 'glyp-file_blank', 'glyp-file_html', 'glyp-file_image', 'glyp-file_pdf', 'glyp-file_printable', 'glyp-file_spreadsheet', 'glyp-file_text', 'glyp-filter', 'glyp-finance', 'glyp-finding', 'glyp-folder', 'glyp-followup', 'glyp-food_bev_pharma', 'glyp-formula', 'glyp-fuel', 'glyp-fumigation', 'glyp-function', 'glyp-function_aggregate', 'glyp-function_time', 'glyp-gain_loss', 'glyp-generate_invoices', 'glyp-generate_orders', 'glyp-geo', 'glyp-geo_heat_map', 'glyp-google', 'glyp-google_calendar', 'glyp-government', 'glyp-grain_seed', 'glyp-grid', 'glyp-grouped', 'glyp-grouping_combine', 'glyp-grouping_separate', 'glyp-has_many', 'glyp-health_care', 'glyp-heat_map', 'glyp-heat_treatment', 'glyp-help', 'glyp-hidden', 'glyp-honeybee_removal', 'glyp-housing', 'glyp-in_progress', 'glyp-incomplete_certificate', 'glyp-industrial', 'glyp-info', 'glyp-information_technology', 'glyp-inspect_map', 'glyp-inspections', 'glyp-insulation', 'glyp-interior', 'glyp-invoice', 'glyp-invoice_review', 'glyp-irrigation_install', 'glyp-irrigation_maintenance', 'glyp-items', 'glyp-job', 'glyp-join', 'glyp-join_inner', 'glyp-join_left', 'glyp-justice', 'glyp-k9_inspection', 'glyp-key', 'glyp-label_bottom', 'glyp-label_left', 'glyp-label_right', 'glyp-label_top', 'glyp-labor', 'glyp-landscape', 'glyp-landscape_maintenance', 'glyp-laptop', 'glyp-lead', 'glyp-lead_listing', 'glyp-lead_source', 'glyp-leads_report', 'glyp-learning_hub', 'glyp-ledger', 'glyp-legal', 'glyp-license', 'glyp-lifetime', 'glyp-line_cap_arrow', 'glyp-line_cap_bar', 'glyp-line_cap_circle', 'glyp-line_cap_none', 'glyp-line_caps', 'glyp-line_style', 'glyp-link', 'glyp-location', 'glyp-location_charge', 'glyp-location_credit', 'glyp-location_kind', 'glyp-location_message', 'glyp-location_origin', 'glyp-location_tags', 'glyp-locations', 'glyp-locked', 'glyp-lodging', 'glyp-log_in', 'glyp-log_out', 'glyp-log_report', 'glyp-logbook', 'glyp-logbook_documents', 'glyp-lot_number', 'glyp-manager', 'glyp-map', 'glyp-markdown', 'glyp-market', 'glyp-materials', 'glyp-mattress_encasement', 'glyp-merge', 'glyp-message_billing', 'glyp-message_collections', 'glyp-message_complaint', 'glyp-message_misc', 'glyp-message_technician', 'glyp-messages', 'glyp-misc', 'glyp-miscellaneous', 'glyp-move_order', 'glyp-mowing', 'glyp-multi_housing', 'glyp-mute', 'glyp-navicon', 'glyp-new_location', 'glyp-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-outlook', 'glyp-overlap', 'glyp-password', 'glyp-past_due', 'glyp-paused', 'glyp-pay_brackets', 'glyp-payment', 'glyp-payment_ach', 'glyp-payment_application_apply', 'glyp-payment_application_correction', 'glyp-payment_application_initial', 'glyp-payment_application_refund', 'glyp-payment_application_transfer', 'glyp-payment_application_unapply', 'glyp-payment_application_unapply_all', 'glyp-payment_applications', 'glyp-payment_batch', 'glyp-payment_cash', 'glyp-payment_check', 'glyp-payment_coupon', 'glyp-payment_credit', 'glyp-payment_discount', 'glyp-payment_eft', 'glyp-payment_finance_charge', 'glyp-payments', 'glyp-payroll', 'glyp-pdf', 'glyp-pending', 'glyp-periodic', 'glyp-periodic_assessment', 'glyp-permission', 'glyp-pest', 'glyp-pest_ants', 'glyp-pest_bats', 'glyp-pest_bed_bugs', 'glyp-pest_birds', 'glyp-pest_crawling_insects', 'glyp-pest_dermestids', 'glyp-pest_fall_invaders', 'glyp-pest_flying_insects', 'glyp-pest_moles', 'glyp-pest_mosquitoes', 'glyp-pest_nuisance_animals', 'glyp-pest_ornamental', 'glyp-pest_ornamental_diseases', 'glyp-pest_roaches', 'glyp-pest_rodents', 'glyp-pest_scorpions', 'glyp-pest_snakes', 'glyp-pest_spiders', 'glyp-pest_stinging_insects', 'glyp-pest_stored_product_pests', 'glyp-pest_ticks_and_fleas', 'glyp-pest_viruses_and_bacteria', 'glyp-pest_weeds', 'glyp-pest_wood_destroyers', 'glyp-phone', 'glyp-pick_date', 'glyp-pick_list', 'glyp-platform_android', 'glyp-platform_ios', 'glyp-platform_macos', 'glyp-platform_windows', 'glyp-play', 'glyp-plus', 'glyp-plus_filled', 'glyp-plus_outline', 'glyp-portal', 'glyp-price_increase', 'glyp-price_increase_alt', 'glyp-pricing_table', 'glyp-print', 'glyp-privacy', 'glyp-product_sale', 'glyp-production', 'glyp-professional_consultation', 'glyp-program', 'glyp-program_elements', 'glyp-program_initiation', 'glyp-program_order', 'glyp-program_review', 'glyp-promo', 'glyp-proposal', 'glyp-protection', 'glyp-purchase_order', 'glyp-quality', 'glyp-query', 'glyp-radio_checked', 'glyp-radio_empty', 'glyp-reassign', 'glyp-receipt', 'glyp-recent', 'glyp-recommendation', 'glyp-record_details', 'glyp-recurring_revenue', 'glyp-redo', 'glyp-refresh', 'glyp-refund', 'glyp-region', 'glyp-released', 'glyp-remove', 'glyp-renewal', 'glyp-report', 'glyp-required_input', 'glyp-reschedule', 'glyp-restaurant_bar', 'glyp-revenue', 'glyp-review', 'glyp-rfid', 'glyp-ride_along', 'glyp-rodent_exclusion', 'glyp-route_change', 'glyp-route_optimization', 'glyp-rows', 'glyp-ruler', 'glyp-sales', 'glyp-sales_contest', 'glyp-sales_pipeline', 'glyp-sales_tax', 'glyp-sales_tax_exemption', 'glyp-schedule_lock', 'glyp-schedule_lock_afternoon', 'glyp-schedule_lock_day', 'glyp-schedule_lock_exact', 'glyp-schedule_lock_four_hour', 'glyp-schedule_lock_morning', 'glyp-schedule_lock_none', 'glyp-schedule_lock_two_day', 'glyp-schedule_lock_two_hour', 'glyp-schedule_lock_week', 'glyp-schedule_tyoe_every', 'glyp-schedule_type_every', 'glyp-schedule_type_none', 'glyp-schedule_type_on_demand', 'glyp-schedule_type_schedule', 'glyp-scheduled', 'glyp-scheduling', 'glyp-school_church', 'glyp-script', 'glyp-search', 'glyp-seeding', 'glyp-segment', 'glyp-sent', 'glyp-sentricon', 'glyp-service_agreement', 'glyp-service_form', 'glyp-service_miscellaneous', 'glyp-service_reminder', 'glyp-service_report', 'glyp-service_request', 'glyp-services', 'glyp-settings', 'glyp-setup', 'glyp-signature', 'glyp-simulator', 'glyp-site_map', 'glyp-site_map_annotation', 'glyp-site_map_chemical', 'glyp-site_map_edit_details', 'glyp-site_map_edit_geo', 'glyp-site_map_equipment', 'glyp-site_map_exterior', 'glyp-site_map_foundation', 'glyp-site_map_icon', 'glyp-site_map_interior', 'glyp-site_map_label', 'glyp-site_map_perimeter', 'glyp-site_map_settings_left', 'glyp-site_map_settings_right', 'glyp-site_map_template', 'glyp-skip', 'glyp-smart_traps', 'glyp-sms', 'glyp-snow_removal', 'glyp-sort', 'glyp-special', 'glyp-split', 'glyp-spp_scorecard', 'glyp-square_edge', 'glyp-start_sheet', 'glyp-start_time', 'glyp-statement', 'glyp-states', 'glyp-sticky', 'glyp-stop_time', 'glyp-store_material', 'glyp-store_order', 'glyp-store_order_back_order', 'glyp-store_order_cancelled', 'glyp-store_order_complete', 'glyp-store_order_pending', 'glyp-store_order_pending_return', 'glyp-store_order_pre_order', 'glyp-store_order_returned', 'glyp-stores', 'glyp-styling', 'glyp-subscribe', 'glyp-supplemental', 'glyp-support', 'glyp-sync', 'glyp-table', 'glyp-tablet', 'glyp-tablets', 'glyp-tag_manager', 'glyp-tags', 'glyp-task_runs', 'glyp-tech_pest_sightings', 'glyp-technician', 'glyp-technician_approach', 'glyp-template', 'glyp-termite_fumigation', 'glyp-terrier', 'glyp-territory', 'glyp-text', 'glyp-thermometer', 'glyp-third_party_billing', 'glyp-ticket_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-unchecked', 'glyp-undo', 'glyp-unit_sweep', 'glyp-unit_tag', 'glyp-university', 'glyp-unlocked', '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
3
+ 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-expiring', 'glyp-exterior', 'glyp-extras', 'glyp-facebook', 'glyp-farm', 'glyp-farm_grain_seed', 'glyp-fast_and_frequent', 'glyp-favorite', 'glyp-feedback', 'glyp-field_guide', 'glyp-field_training', 'glyp-file_blank', 'glyp-file_html', 'glyp-file_image', 'glyp-file_pdf', 'glyp-file_printable', 'glyp-file_spreadsheet', 'glyp-file_text', 'glyp-filter', 'glyp-finance', 'glyp-finding', 'glyp-folder', 'glyp-followup', 'glyp-food_bev_pharma', 'glyp-formula', 'glyp-fuel', 'glyp-fumigation', 'glyp-function', 'glyp-function_aggregate', 'glyp-function_time', 'glyp-gain_loss', 'glyp-generate_invoices', 'glyp-generate_orders', 'glyp-geo', 'glyp-geo_heat_map', 'glyp-google', 'glyp-google_calendar', 'glyp-government', 'glyp-grain_seed', 'glyp-grid', 'glyp-grouped', 'glyp-grouping_combine', 'glyp-grouping_separate', 'glyp-has_many', 'glyp-health_care', 'glyp-heat_map', 'glyp-heat_treatment', 'glyp-help', 'glyp-hidden', 'glyp-honeybee_removal', 'glyp-housing', 'glyp-in_progress', 'glyp-incomplete_certificate', 'glyp-industrial', 'glyp-info', 'glyp-information_technology', 'glyp-inspect_map', 'glyp-inspections', 'glyp-insulation', 'glyp-interior', 'glyp-invoice', 'glyp-invoice_review', 'glyp-irrigation_install', 'glyp-irrigation_maintenance', 'glyp-items', 'glyp-job', 'glyp-join', 'glyp-join_inner', 'glyp-join_left', 'glyp-justice', 'glyp-k9_inspection', 'glyp-key', 'glyp-label_bottom', 'glyp-label_left', 'glyp-label_right', 'glyp-label_top', 'glyp-labor', 'glyp-landscape', 'glyp-landscape_maintenance', 'glyp-laptop', 'glyp-lead', 'glyp-lead_listing', 'glyp-lead_source', 'glyp-leads_report', 'glyp-learning_hub', 'glyp-ledger', 'glyp-legal', 'glyp-license', 'glyp-lifetime', 'glyp-line_cap_arrow', 'glyp-line_cap_bar', 'glyp-line_cap_circle', 'glyp-line_cap_none', 'glyp-line_caps', 'glyp-line_style', 'glyp-link', 'glyp-location', 'glyp-location_charge', 'glyp-location_credit', 'glyp-location_kind', 'glyp-location_message', 'glyp-location_origin', 'glyp-location_tags', 'glyp-locations', 'glyp-locked', 'glyp-lodging', 'glyp-log_in', 'glyp-log_out', 'glyp-log_report', 'glyp-logbook', 'glyp-logbook_documents', 'glyp-lot_number', 'glyp-manager', 'glyp-map', 'glyp-markdown', 'glyp-market', 'glyp-materials', 'glyp-mattress_encasement', 'glyp-merge', 'glyp-message_billing', 'glyp-message_collections', 'glyp-message_complaint', 'glyp-message_misc', 'glyp-message_technician', 'glyp-messages', 'glyp-misc', 'glyp-miscellaneous', 'glyp-move_order', 'glyp-mowing', 'glyp-multi_housing', 'glyp-mute', 'glyp-navicon', 'glyp-new_location', 'glyp-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-outlook', 'glyp-overlap', 'glyp-password', 'glyp-past_due', 'glyp-paused', 'glyp-pay_brackets', 'glyp-payment', 'glyp-payment_ach', 'glyp-payment_application_apply', 'glyp-payment_application_correction', 'glyp-payment_application_initial', 'glyp-payment_application_refund', 'glyp-payment_application_transfer', 'glyp-payment_application_unapply', 'glyp-payment_application_unapply_all', 'glyp-payment_applications', 'glyp-payment_batch', 'glyp-payment_cash', 'glyp-payment_check', 'glyp-payment_coupon', 'glyp-payment_credit', 'glyp-payment_discount', 'glyp-payment_eft', 'glyp-payment_finance_charge', 'glyp-payments', 'glyp-payroll', 'glyp-pdf', 'glyp-pending', 'glyp-periodic', 'glyp-periodic_assessment', 'glyp-permission', 'glyp-pest', 'glyp-pest_ants', 'glyp-pest_bats', 'glyp-pest_bed_bugs', 'glyp-pest_birds', 'glyp-pest_crawling_insects', 'glyp-pest_dermestids', 'glyp-pest_fall_invaders', 'glyp-pest_flying_insects', 'glyp-pest_moles', 'glyp-pest_mosquitoes', 'glyp-pest_nuisance_animals', 'glyp-pest_ornamental', 'glyp-pest_ornamental_diseases', 'glyp-pest_roaches', 'glyp-pest_rodents', 'glyp-pest_scorpions', 'glyp-pest_snakes', 'glyp-pest_spiders', 'glyp-pest_stinging_insects', 'glyp-pest_stored_product_pests', 'glyp-pest_ticks_and_fleas', 'glyp-pest_viruses_and_bacteria', 'glyp-pest_weeds', 'glyp-pest_wood_destroyers', 'glyp-phone', 'glyp-pick_date', 'glyp-pick_list', 'glyp-platform_android', 'glyp-platform_ios', 'glyp-platform_macos', 'glyp-platform_windows', 'glyp-play', 'glyp-plus', 'glyp-plus_filled', 'glyp-plus_outline', 'glyp-portal', 'glyp-price_increase', 'glyp-price_increase_alt', 'glyp-pricing_table', 'glyp-print', 'glyp-privacy', 'glyp-product_sale', 'glyp-production', 'glyp-professional_consultation', 'glyp-program', 'glyp-program_elements', 'glyp-program_initiation', 'glyp-program_order', 'glyp-program_review', 'glyp-promo', 'glyp-proposal', 'glyp-protection', 'glyp-purchase_order', 'glyp-quality', 'glyp-query', 'glyp-radio_checked', 'glyp-radio_empty', 'glyp-reassign', 'glyp-receipt', 'glyp-recent', 'glyp-recommendation', 'glyp-record_details', 'glyp-recurring_revenue', 'glyp-redo', 'glyp-refresh', 'glyp-refund', 'glyp-region', 'glyp-released', 'glyp-remove', 'glyp-renewal', 'glyp-report', 'glyp-required_input', 'glyp-reschedule', 'glyp-restaurant_bar', 'glyp-revenue', 'glyp-review', 'glyp-rfid', 'glyp-ride_along', 'glyp-rodent_exclusion', 'glyp-route_change', 'glyp-route_optimization', 'glyp-rows', 'glyp-ruler', 'glyp-sales', 'glyp-sales_contest', 'glyp-sales_pipeline', 'glyp-sales_tax', 'glyp-sales_tax_exemption', 'glyp-schedule_lock', 'glyp-schedule_lock_afternoon', 'glyp-schedule_lock_day', 'glyp-schedule_lock_exact', 'glyp-schedule_lock_four_hour', 'glyp-schedule_lock_morning', 'glyp-schedule_lock_none', 'glyp-schedule_lock_two_day', 'glyp-schedule_lock_two_hour', 'glyp-schedule_lock_week', 'glyp-schedule_tyoe_every', 'glyp-schedule_type_every', 'glyp-schedule_type_none', 'glyp-schedule_type_on_demand', 'glyp-schedule_type_schedule', 'glyp-scheduled', 'glyp-scheduling', 'glyp-school_church', 'glyp-script', 'glyp-search', 'glyp-seeding', 'glyp-segment', 'glyp-sent', 'glyp-sentricon', 'glyp-service_agreement', 'glyp-service_form', 'glyp-service_miscellaneous', 'glyp-service_reminder', 'glyp-service_report', 'glyp-service_request', 'glyp-services', 'glyp-settings', 'glyp-setup', 'glyp-signature', 'glyp-simulator', 'glyp-site_map', 'glyp-site_map_annotation', 'glyp-site_map_chemical', 'glyp-site_map_edit_details', 'glyp-site_map_edit_geo', 'glyp-site_map_equipment', 'glyp-site_map_exterior', 'glyp-site_map_foundation', 'glyp-site_map_icon', 'glyp-site_map_interior', 'glyp-site_map_label', 'glyp-site_map_perimeter', 'glyp-site_map_settings_left', 'glyp-site_map_settings_right', 'glyp-site_map_template', 'glyp-skip', 'glyp-smart_traps', 'glyp-sms', 'glyp-snow_removal', 'glyp-sort', 'glyp-special', 'glyp-split', 'glyp-spp_scorecard', 'glyp-square_edge', 'glyp-start_sheet', 'glyp-start_time', 'glyp-statement', 'glyp-states', 'glyp-sticky', 'glyp-stop_time', 'glyp-store_material', 'glyp-store_order', 'glyp-store_order_back_order', 'glyp-store_order_cancelled', 'glyp-store_order_complete', 'glyp-store_order_pending', 'glyp-store_order_pending_return', 'glyp-store_order_pre_order', 'glyp-store_order_returned', 'glyp-stores', 'glyp-styling', 'glyp-subscribe', 'glyp-supplemental', 'glyp-support', 'glyp-sync', 'glyp-table', 'glyp-tablet', 'glyp-tablets', 'glyp-tag_manager', 'glyp-tags', 'glyp-task_runs', 'glyp-tech_pest_sightings', 'glyp-technician', 'glyp-technician_approach', 'glyp-template', 'glyp-termite_fumigation', 'glyp-terrier', 'glyp-territory', 'glyp-text', 'glyp-thermometer', 'glyp-third_party_billing', 'glyp-ticket_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-unchecked', 'glyp-undo', 'glyp-unit_sweep', 'glyp-unit_tag', 'glyp-university', 'glyp-unlocked', '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
4
4
 
5
5
  const Glyps = {
6
6
  names
package/ids.ts ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Makes a new UUID.
3
+ */
4
+ function makeUuid(): string {
5
+ return URL.createObjectURL(new Blob([])).slice(-36)
6
+ }
7
+
8
+ const Ids = {
9
+ makeUuid
10
+ }
11
+
12
+ export default Ids
package/modals.ts CHANGED
@@ -44,7 +44,11 @@ export abstract class ModalPart<TState> extends ContentPart<TState> {
44
44
  const secondaryActions = this.getActions('secondary')
45
45
  const primaryActions = this.getActions('primary')
46
46
  if (secondaryActions.length || primaryActions.length) {
47
- parent.div('.modal-actions', actions => {
47
+ const actionsClasses = ['modal-actions']
48
+ if (this._actionLoading) {
49
+ actionsClasses.push('loading')
50
+ }
51
+ parent.div(...actionsClasses, actions => {
48
52
  actions.div('.secondary-actions', container => {
49
53
  this.theme.renderActions(container, secondaryActions, {iconColor: 'white', defaultClass: 'secondary'})
50
54
  })
@@ -55,6 +59,36 @@ export abstract class ModalPart<TState> extends ContentPart<TState> {
55
59
  }
56
60
  }
57
61
 
62
+ private _actionLoading = false
63
+
64
+ /**
65
+ * Blurs the actions to indicate that the modal is doing something.
66
+ */
67
+ startActionLoading() {
68
+ const elem = this.element
69
+ if (elem) {
70
+ const actions = elem.querySelector('.modal-actions')
71
+ if (actions) {
72
+ this._actionLoading = true
73
+ actions.classList.add('loading')
74
+ }
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Call after calling `startActionLoading()` to remove the loading effect.
80
+ */
81
+ stopActionLoading() {
82
+ this._actionLoading = false
83
+ const elem = this.element
84
+ if (elem) {
85
+ const actions = elem.querySelector('.modal-actions')
86
+ if (actions) {
87
+ actions.classList.remove('loading')
88
+ }
89
+ }
90
+ }
91
+
58
92
  }
59
93
 
60
94
 
package/overlays.ts CHANGED
@@ -9,7 +9,7 @@ const log = new Logger('Overlays')
9
9
  // Part
10
10
  ////////////////////////////////////////////////////////////////////////////////
11
11
 
12
- const OverlayLayerTypes = ['modal', 'dropdown', 'lightbox', 'jump'] as const
12
+ const OverlayLayerTypes = ['modal', 'dropdown', 'lightbox', 'jump', 'sheet'] as const
13
13
 
14
14
  /**
15
15
  * The type of overlay for any given layer.
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "files": [
5
5
  "*"
6
6
  ],
7
- "version": "4.3.0",
7
+ "version": "4.4.0",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "https://github.com/Terrier-Tech/terrier-engine"
@@ -15,9 +15,11 @@
15
15
  "test": "vitest"
16
16
  },
17
17
  "dependencies": {
18
+ "@types/turbolinks": "^5.2.0",
18
19
  "dayjs": "^1.11.7",
19
20
  "inflection": "^2.0.1",
20
- "tuff-core": "latest"
21
+ "tuff-core": "latest",
22
+ "turbolinks": "^5.2.0"
21
23
  },
22
24
  "devDependencies": {
23
25
  "@types/node": "^18.16.3",
@@ -1,8 +1,10 @@
1
- import {Part} from "tuff-core/parts"
1
+ import {Part, PartTag} from "tuff-core/parts"
2
2
  import {TerrierApp} from "../app"
3
3
  import Loading from "../loading"
4
- import Theme from "../theme"
4
+ import Theme, {IconName} from "../theme"
5
5
  import Toasts, {ToastOptions} from "../toasts"
6
+ import {DbErrors} from "../db-client"
7
+ import inflection from "inflection";
6
8
 
7
9
  /**
8
10
  * Base class for ALL parts in a Terrier application.
@@ -75,6 +77,20 @@ export default abstract class TerrierPart<TState> extends Part<TState> {
75
77
  }
76
78
 
77
79
 
80
+ /// Errors
81
+
82
+ renderErrorBubble(parent: PartTag, errors: DbErrors<any>) {
83
+ parent.div('.tt-bubble.alert', bubble => {
84
+ bubble.ul(ul => {
85
+ for (const kv of Object.entries(errors)) {
86
+ const name = inflection.titleize(kv[0])
87
+ ul.li().text(`${name} ${kv[1]}`)
88
+ }
89
+ })
90
+ })
91
+ }
92
+
93
+
78
94
  /// Toasts
79
95
 
80
96
  /**
@@ -86,4 +102,31 @@ export default abstract class TerrierPart<TState> extends Part<TState> {
86
102
  Toasts.show(message, options, this.theme)
87
103
  }
88
104
 
105
+ /**
106
+ * Show an alert toast with the given message.
107
+ * @param message
108
+ * @param icon
109
+ */
110
+ alertToast(message: string, icon?: IconName) {
111
+ this.showToast(message, {icon, color: 'alert'})
112
+ }
113
+
114
+ /**
115
+ * Show an info toast with the given message.
116
+ * @param message
117
+ * @param icon
118
+ */
119
+ infoToast(message: string, icon?: IconName) {
120
+ this.showToast(message, {icon, color: 'primary'})
121
+ }
122
+
123
+ /**
124
+ * Show a success toast with the given message.
125
+ * @param message
126
+ * @param icon
127
+ */
128
+ successToast(message: string, icon?: IconName) {
129
+ this.showToast(message, {icon, color: 'success'})
130
+ }
131
+
89
132
  }
package/schema.ts CHANGED
@@ -38,10 +38,15 @@ export type HasManyDef = {
38
38
  * Definition for a single model in the schema.
39
39
  */
40
40
  export type ModelDef = {
41
+ name: string
41
42
  table_name: string
42
43
  columns: Record<string, ColumnDef>
43
44
  belongs_to: Record<string, BelongsToDef>
44
45
  has_many: Record<string, HasManyDef>
46
+ metadata?: {
47
+ description?: string
48
+ common?: boolean
49
+ }
45
50
  }
46
51
 
47
52
  /**
@@ -82,13 +87,36 @@ function belongsToDisplay(belongsTo: BelongsToDef): string {
82
87
  }
83
88
 
84
89
 
90
+ ////////////////////////////////////////////////////////////////////////////////
91
+ // Meta
92
+ ////////////////////////////////////////////////////////////////////////////////
93
+
94
+ /**
95
+ * Gets all models with common=true in the metadata.
96
+ * @param schema
97
+ */
98
+ function commonModels(schema: SchemaDef): ModelDef[] {
99
+ return Object.values(schema.models).filter(m => m.metadata?.common)
100
+ }
101
+
102
+ /**
103
+ * Gets all models with common=false (or not defined) in the metadata.
104
+ * @param schema
105
+ */
106
+ function uncommonModels(schema: SchemaDef): ModelDef[] {
107
+ return Object.values(schema.models).filter(m => !(m.metadata?.common))
108
+ }
109
+
110
+
85
111
  ////////////////////////////////////////////////////////////////////////////////
86
112
  // Export
87
113
  ////////////////////////////////////////////////////////////////////////////////
88
114
 
89
115
  const Schema = {
90
116
  get,
91
- belongsToDisplay
117
+ belongsToDisplay,
118
+ commonModels,
119
+ uncommonModels
92
120
  }
93
121
 
94
122
  export default Schema
package/sheets.ts ADDED
@@ -0,0 +1,100 @@
1
+ import {Action, IconName} from "./theme"
2
+ import TerrierPart from "./parts/terrier-part"
3
+ import {PartTag} from "tuff-core/parts"
4
+ import Fragments from "./fragments"
5
+ import {messages} from "tuff-core"
6
+ import {Logger} from "tuff-core/logging"
7
+
8
+ const log = new Logger('Sheets')
9
+
10
+
11
+ ////////////////////////////////////////////////////////////////////////////////
12
+ // Parts
13
+ ////////////////////////////////////////////////////////////////////////////////
14
+
15
+ export type SheetState = {
16
+ title: string
17
+ icon: IconName
18
+ body: string
19
+ primaryActions?: Action[]
20
+ secondaryActions?: Action[]
21
+ }
22
+
23
+ const clearKey = messages.untypedKey()
24
+
25
+ export class Sheet<TState extends SheetState> extends TerrierPart<TState> {
26
+
27
+ /**
28
+ * Removes itself from the DOM.
29
+ */
30
+ clear() {
31
+ log.info("Clearing sheet")
32
+ this.app.removeOverlay(this.state)
33
+ }
34
+
35
+ async init() {
36
+ this.onClick(clearKey, _ => {
37
+ this.clear()
38
+ })
39
+ }
40
+
41
+ get parentClasses(): Array<string> {
42
+ return ['tt-sheet']
43
+ }
44
+
45
+ render(parent: PartTag): any {
46
+ parent.div('.tt-sheet-backdrop')
47
+ const panel = Fragments.panel(this.theme)
48
+ .title(this.state.title)
49
+ .icon(this.state.icon)
50
+ .content(panel => {
51
+ panel.class('padded')
52
+ panel.div('.body').text(this.state.body)
53
+ })
54
+ for (const action of this.state.primaryActions || []) {
55
+ // if it doesn't have a click key, it must be a close button
56
+ action.click ||= {key: clearKey}
57
+ panel.addAction(action, 'primary')
58
+ }
59
+ for (const action of this.state.secondaryActions || []) {
60
+ // if it doesn't have a click key, it must be a close button
61
+ action.click ||= {key: clearKey}
62
+ panel.addAction(action, 'secondary')
63
+ }
64
+ panel.render(parent)
65
+ }
66
+
67
+ update(_elem: HTMLElement) {
68
+ setTimeout(
69
+ () => _elem.classList.add('show'),
70
+ 10
71
+ )
72
+
73
+ }
74
+ }
75
+
76
+
77
+ ////////////////////////////////////////////////////////////////////////////////
78
+ // Types
79
+ ////////////////////////////////////////////////////////////////////////////////
80
+
81
+ /**
82
+ * State type for a sheet that asks the user to confirm a choice.
83
+ */
84
+ export type ConfirmSheetState = Pick<SheetState, 'title' | 'body' | 'icon'>
85
+
86
+ /**
87
+ * State type for a sheet that tells the user something with no options.
88
+ */
89
+ export type AlertSheetState = Pick<SheetState, 'title' | 'body' | 'icon'>
90
+
91
+
92
+ ////////////////////////////////////////////////////////////////////////////////
93
+ // Export
94
+ ////////////////////////////////////////////////////////////////////////////////
95
+
96
+ const Sheets = {
97
+ clearKey
98
+ }
99
+
100
+ export default Sheets
package/tabs.ts CHANGED
@@ -2,7 +2,7 @@ import {Logger} from "tuff-core/logging"
2
2
  import {typedKey} from "tuff-core/messages"
3
3
  import {Part, PartParent, PartTag, StatelessPart} from "tuff-core/parts"
4
4
  import TerrierPart from "./parts/terrier-part"
5
- import {Action, IconName} from "./theme";
5
+ import {Action, IconName, Packet} from "./theme"
6
6
 
7
7
  const log = new Logger("Tabs")
8
8
 
@@ -14,6 +14,8 @@ export type TabParams = {
14
14
  title: string
15
15
  icon?: IconName
16
16
  state?: 'enabled' | 'disabled' | 'hidden'
17
+ classes?: string[]
18
+ click?: Packet
17
19
  }
18
20
 
19
21
  /**
@@ -81,6 +83,24 @@ export class TabContainerPart extends TerrierPart<TabContainerState> {
81
83
  this.dirty()
82
84
  }
83
85
 
86
+ /**
87
+ * Removes the tab with the given key.
88
+ * @param key
89
+ */
90
+ removeTab(key: string) {
91
+ const tab = this.tabs[key]
92
+ if (tab) {
93
+ log.info(`Removing tab ${key}`, tab)
94
+ delete this.tabs[key]
95
+ this.removeChild(tab.part)
96
+ this.state.currentTab = undefined
97
+ this.dirty()
98
+ }
99
+ else {
100
+ log.warn(`No tab ${key} to remove!`)
101
+ }
102
+ }
103
+
84
104
  /**
85
105
  * Changes this tab container to show the tab with the given key
86
106
  * @param tabKey
@@ -137,6 +157,9 @@ export class TabContainerPart extends TerrierPart<TabContainerState> {
137
157
  }
138
158
  a.span({text: tab.title})
139
159
  a.emitClick(this.changeTabKey, {tabKey: tab.key})
160
+ if (tab.click) {
161
+ a.emitClick(tab.click.key, tab.click.data || {})
162
+ }
140
163
  })
141
164
  }
142
165
  if (this._afterAction) {
@@ -146,9 +169,12 @@ export class TabContainerPart extends TerrierPart<TabContainerState> {
146
169
  })
147
170
 
148
171
  if (currentTabKey) {
149
- const currentTabPart = this.tabs[currentTabKey].part
172
+ const currentTab = this.tabs[currentTabKey]
150
173
  container.div('.tt-tab-content', panel => {
151
- panel.part(currentTabPart as StatelessPart)
174
+ if (currentTab.classes?.length) {
175
+ panel.class(...currentTab.classes)
176
+ }
177
+ panel.part(currentTab.part as StatelessPart)
152
178
  })
153
179
  }
154
180
  })
package/theme.ts CHANGED
@@ -11,8 +11,8 @@ export interface ThemeType {
11
11
 
12
12
  export type IconName = GlypName | HubIconName
13
13
 
14
- const ColorNames = [
15
- 'link', 'primary', 'secondary', 'active', 'pending', 'success', 'alert', 'white', 'inactive'
14
+ export const ColorNames = [
15
+ 'link', 'primary', 'secondary', 'active', 'pending', 'success', 'warn', 'alert', 'white', 'inactive', 'super', 'billing', 'docs'
16
16
  ] as const
17
17
 
18
18
  export type ColorName = typeof ColorNames[number]
@@ -55,16 +55,13 @@ export default class Theme {
55
55
  HubIcons.renderIcon(parent, icon as HubIconName, color)
56
56
  }
57
57
  else { // a regular font icon
58
- const classes: string[] = [icon]
59
- if (color?.length) {
60
- classes.push(color)
61
- }
62
- parent.i().class(...classes)
58
+ const iconElem = parent.i('.icon', icon)
59
+ if (color?.length) iconElem.class(color)
63
60
  }
64
61
  }
65
62
 
66
63
  renderCloseIcon(parent: PartTag, color?: ColorName | null): void {
67
- const classes = ['glyp-close', 'close']
64
+ const classes = ['icon', 'glyp-close', 'close']
68
65
  if (color?.length) {
69
66
  classes.push(color)
70
67
  }
@@ -101,6 +98,9 @@ export default class Theme {
101
98
  if (action.title?.length) {
102
99
  a.div('.title', {text: action.title})
103
100
  }
101
+ else {
102
+ a.class('icon-only')
103
+ }
104
104
  if (action.href?.length) {
105
105
  a.attrs({href: action.href})
106
106
  }