terrier-engine 4.0.16 → 4.0.18

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/app.ts CHANGED
@@ -16,11 +16,19 @@ Logger.level = 'info'
16
16
  /**
17
17
  * Main application part that renders the entire page.
18
18
  */
19
- export abstract class TerrierApp<TT extends ThemeType> extends TerrierPart<{theme: Theme<TT>}, TT> {
20
-
21
- _theme!: Theme<TT>
22
-
23
- get theme(): Theme<TT> {
19
+ export abstract class TerrierApp<
20
+ TThemeType extends ThemeType,
21
+ TTheme extends Theme<TThemeType>
22
+ > extends TerrierPart<
23
+ {theme: TTheme},
24
+ TThemeType,
25
+ TerrierApp<TThemeType, TTheme>,
26
+ TTheme
27
+ > {
28
+
29
+ _theme!: TTheme
30
+
31
+ get theme(): TTheme {
24
32
  return this._theme
25
33
  }
26
34
 
@@ -68,9 +76,9 @@ export abstract class TerrierApp<TT extends ThemeType> extends TerrierPart<{them
68
76
 
69
77
  /// Modals
70
78
 
71
- showModal<ModalType extends ModalPart<StateType, TT>, StateType>(constructor: { new(p: PartParent, id: string, state: StateType): ModalType; }, state: StateType): ModalType {
79
+ showModal<ModalType extends ModalPart<StateType, TThemeType>, StateType>(constructor: { new(p: PartParent, id: string, state: StateType): ModalType; }, state: StateType): ModalType {
72
80
  const modalStack =
73
- (this.overlayPart.parts.modal as ModalStackPart<TT>)
81
+ (this.overlayPart.parts.modal as ModalStackPart<TThemeType>)
74
82
  ?? this.makeOverlay(ModalStackPart, {}, 'modal')
75
83
  const modal = modalStack.pushModal(constructor, state)
76
84
  modalStack.dirty()
package/dropdowns.ts CHANGED
@@ -5,7 +5,8 @@ import {PartTag, StatelessPart} from "tuff-core/parts"
5
5
  import Overlays from "./overlays"
6
6
  import {TerrierPart} from "./parts"
7
7
  import Objects from "tuff-core/objects"
8
- import {Action, ThemeType} from "./theme"
8
+ import Theme, {Action, ThemeType} from "./theme"
9
+ import {TerrierApp} from "./app";
9
10
 
10
11
  const log = new Logger('Dropdowns')
11
12
 
@@ -15,7 +16,7 @@ const clearDropdownKey = untypedKey()
15
16
  * Abstract base class for dropdown parts.
16
17
  * Subclasses must implement the `renderContent()` method to render the dropdown content.
17
18
  */
18
- export abstract class Dropdown<T, TT extends ThemeType> extends TerrierPart<T, TT> {
19
+ export abstract class Dropdown<T, TT extends ThemeType> extends TerrierPart<T, TT, TerrierApp<TT, Theme<TT>>, Theme<TT>> {
19
20
 
20
21
  parentPart?: StatelessPart
21
22
 
package/lightbox.ts CHANGED
@@ -2,6 +2,7 @@ import { Logger } from "tuff-core/logging"
2
2
  import { untypedKey } from "tuff-core/messages"
3
3
  import {Part, PartTag} from "tuff-core/parts"
4
4
  import {TerrierApp} from "./app"
5
+ import Theme, {ThemeType} from "./theme";
5
6
 
6
7
  const log = new Logger('Lightbox')
7
8
 
@@ -12,9 +13,10 @@ const log = new Logger('Lightbox')
12
13
  /**
13
14
  * Initializes a global event listener for image elements contained in the `containerClass`
14
15
  * @param root
16
+ * @param app
15
17
  * @param containerClass
16
18
  */
17
- function init(root: HTMLElement, app: TerrierApp<any>, containerClass: string) {
19
+ function init<TT extends ThemeType>(root: HTMLElement, app: TerrierApp<TT, Theme<TT>>, containerClass: string) {
18
20
  log.info("Init", root)
19
21
  root.addEventListener("click", evt => {
20
22
  if (!(evt.target instanceof HTMLElement) || evt.target.tagName != 'IMG') {
@@ -47,13 +49,13 @@ function init(root: HTMLElement, app: TerrierApp<any>, containerClass: string) {
47
49
 
48
50
  type LightboxState = { src: string }
49
51
 
50
- function showPart(app: TerrierApp<any>, state: LightboxState) {
52
+ function showPart<TT extends ThemeType>(app: TerrierApp<TT, Theme<TT>>, state: LightboxState) {
51
53
  app.makeOverlay(LightboxPart, {app,...state}, 'lightbox')
52
54
  }
53
55
 
54
56
  const closeKey = untypedKey()
55
57
 
56
- class LightboxPart extends Part<LightboxState & {app: TerrierApp<any>}> {
58
+ class LightboxPart<TT extends ThemeType> extends Part<LightboxState & {app: TerrierApp<TT, Theme<TT>>}> {
57
59
 
58
60
  async init() {
59
61
  this.onClick(closeKey, _ => {
package/modals.ts CHANGED
@@ -2,7 +2,8 @@ import { Logger } from "tuff-core/logging"
2
2
  import { untypedKey } from "tuff-core/messages"
3
3
  import {ContentPart, TerrierPart} from "./parts"
4
4
  import {PartParent, PartTag} from "tuff-core/parts"
5
- import {ThemeType} from "./theme";
5
+ import Theme, {ThemeType} from "./theme";
6
+ import {TerrierApp} from "./app";
6
7
 
7
8
  const log = new Logger('Modals')
8
9
 
@@ -67,7 +68,7 @@ export abstract class ModalPart<T, TT extends ThemeType> extends ContentPart<T,
67
68
  */
68
69
  export const modalPopKey = untypedKey()
69
70
 
70
- export class ModalStackPart<TT extends ThemeType> extends TerrierPart<{}, TT> {
71
+ export class ModalStackPart<TT extends ThemeType> extends TerrierPart<{}, TT, TerrierApp<TT, Theme<TT>>, Theme<TT>> {
71
72
 
72
73
  displayClass: 'show' | 'hide' = 'show'
73
74
 
package/package.json CHANGED
@@ -4,14 +4,14 @@
4
4
  "files": [
5
5
  "*"
6
6
  ],
7
- "version": "4.0.16",
7
+ "version": "4.0.18",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "https://github.com/Terrier-Tech/terrier-engine"
11
11
  },
12
12
  "license": "MIT",
13
13
  "scripts": {
14
- "check": "tsc -p app/frontend -noEmit",
14
+ "check": "tsc -p app/frontend --noEmit",
15
15
  "test": "vitest"
16
16
  },
17
17
  "dependencies": {
package/parts.ts CHANGED
@@ -5,7 +5,7 @@ import Fragments from "./fragments"
5
5
  import {Dropdown} from "./dropdowns"
6
6
  import {TerrierApp} from "./app"
7
7
  import Loading from "./loading"
8
- import Theme, {Action, ThemeType} from "./theme"
8
+ import Theme, {Action, RenderActionOptions, ThemeType} from "./theme"
9
9
  import Toasts, {ToastOptions} from "./toasts";
10
10
 
11
11
  const log = new Logger('Parts')
@@ -26,13 +26,18 @@ export type ActionLevel = keyof PanelActions<any>
26
26
  /**
27
27
  * Base class for ALL parts in a Terrier application.
28
28
  */
29
- export abstract class TerrierPart<T, TT extends ThemeType> extends Part<T> {
30
-
31
- get app(): TerrierApp<TT> {
32
- return this.root as TerrierApp<TT> // this should always be true
29
+ export abstract class TerrierPart<
30
+ TState,
31
+ TThemeType extends ThemeType,
32
+ TApp extends TerrierApp<TThemeType, TTheme>,
33
+ TTheme extends Theme<TThemeType>
34
+ > extends Part<TState> {
35
+
36
+ get app(): TApp {
37
+ return this.root as TApp // this should always be true
33
38
  }
34
39
 
35
- get theme(): Theme<TT> {
40
+ get theme(): TTheme {
36
41
  return this.app.theme
37
42
  }
38
43
 
@@ -101,7 +106,7 @@ export abstract class TerrierPart<T, TT extends ThemeType> extends Part<T> {
101
106
  * @param message the message text
102
107
  * @param options
103
108
  */
104
- showToast(message: string, options: ToastOptions<TT>) {
109
+ showToast(message: string, options: ToastOptions<TThemeType>) {
105
110
  Toasts.show(message, options, this.theme)
106
111
  }
107
112
 
@@ -115,7 +120,12 @@ export abstract class TerrierPart<T, TT extends ThemeType> extends Part<T> {
115
120
  /**
116
121
  * Base class for all Parts that render some main content, like pages, panels, and modals.
117
122
  */
118
- export abstract class ContentPart<T, TT extends ThemeType> extends TerrierPart<T, TT> {
123
+ export abstract class ContentPart<TState, TThemeType extends ThemeType> extends TerrierPart<
124
+ TState,
125
+ TThemeType,
126
+ TerrierApp<TThemeType, Theme<TThemeType>>,
127
+ Theme<TThemeType>
128
+ > {
119
129
 
120
130
  /**
121
131
  * All ContentParts must implement this to render their actual content.
@@ -123,10 +133,6 @@ export abstract class ContentPart<T, TT extends ThemeType> extends TerrierPart<T
123
133
  */
124
134
  abstract renderContent(parent: PartTag): void
125
135
 
126
- get app(): TerrierApp<TT> {
127
- return this.root as TerrierApp<TT> // this should always be true
128
- }
129
-
130
136
 
131
137
  protected _title = ''
132
138
 
@@ -138,9 +144,9 @@ export abstract class ContentPart<T, TT extends ThemeType> extends TerrierPart<T
138
144
  this._title = title
139
145
  }
140
146
 
141
- protected _icon: TT['icons'] | null = null
147
+ protected _icon: TThemeType['icons'] | null = null
142
148
 
143
- setIcon(icon: TT['icons']) {
149
+ setIcon(icon: TThemeType['icons']) {
144
150
  this._icon = icon
145
151
  }
146
152
 
@@ -155,12 +161,12 @@ export abstract class ContentPart<T, TT extends ThemeType> extends TerrierPart<T
155
161
 
156
162
  // stored actions can be either an action object or a reference to a named action
157
163
  actions = {
158
- primary: Array<Action<TT> | string>(),
159
- secondary: Array<Action<TT> | string>(),
160
- tertiary: Array<Action<TT> | string>()
164
+ primary: Array<Action<TThemeType> | string>(),
165
+ secondary: Array<Action<TThemeType> | string>(),
166
+ tertiary: Array<Action<TThemeType> | string>()
161
167
  }
162
168
 
163
- namedActions: Record<string, { action: Action<TT>, level: ActionLevel }> = {}
169
+ namedActions: Record<string, { action: Action<TThemeType>, level: ActionLevel }> = {}
164
170
 
165
171
  /**
166
172
  * Add an action to the part, or replace a named action if it already exists.
@@ -168,7 +174,7 @@ export abstract class ContentPart<T, TT extends ThemeType> extends TerrierPart<T
168
174
  * @param level whether it's a primary, secondary, or tertiary action
169
175
  * @param name a name to be given to this action, so it can be accessed later
170
176
  */
171
- addAction(action: Action<TT>, level: ActionLevel = 'primary', name?: string) {
177
+ addAction(action: Action<TThemeType>, level: ActionLevel = 'primary', name?: string) {
172
178
  if (name?.length) {
173
179
  if (name in this.namedActions) {
174
180
  const currentLevel = this.namedActions[name].level
@@ -191,7 +197,7 @@ export abstract class ContentPart<T, TT extends ThemeType> extends TerrierPart<T
191
197
  * Returns the action definition for the action with the given name, or undefined if there is no action with that name
192
198
  * @param name
193
199
  */
194
- getNamedAction(name: string): Action<TT> | undefined {
200
+ getNamedAction(name: string): Action<TThemeType> | undefined {
195
201
  return this.namedActions[name].action
196
202
  }
197
203
 
@@ -222,7 +228,7 @@ export abstract class ContentPart<T, TT extends ThemeType> extends TerrierPart<T
222
228
  this.actions[level] = []
223
229
  }
224
230
 
225
- getAllActions(): PanelActions<TT> {
231
+ getAllActions(): PanelActions<TThemeType> {
226
232
  return {
227
233
  primary: this.getActions('primary'),
228
234
  secondary: this.getActions('secondary'),
@@ -230,7 +236,7 @@ export abstract class ContentPart<T, TT extends ThemeType> extends TerrierPart<T
230
236
  }
231
237
  }
232
238
 
233
- getActions(level: ActionLevel): Action<TT>[] {
239
+ getActions(level: ActionLevel): Action<TThemeType>[] {
234
240
  return this.actions[level].map(action => {
235
241
  return (typeof action === 'string') ? this.namedActions[action].action : action
236
242
  })
@@ -247,14 +253,14 @@ export abstract class ContentPart<T, TT extends ThemeType> extends TerrierPart<T
247
253
  * @param state the dropdown's state
248
254
  * @param target the target element around which to show the dropdown
249
255
  */
250
- makeDropdown<DropdownType extends Dropdown<DropdownStateType, TT>, DropdownStateType>(
256
+ makeDropdown<DropdownType extends Dropdown<DropdownStateType, TThemeType>, DropdownStateType>(
251
257
  constructor: {new(p: PartParent, id: string, state: DropdownStateType): DropdownType;},
252
258
  state: DropdownStateType,
253
259
  target: EventTarget | null) {
254
260
  if (!(target && target instanceof HTMLElement)) {
255
261
  throw "Trying to show a dropdown without an element target!"
256
262
  }
257
- const dropdown = this.app.makeOverlay(constructor, state, 'dropdown') as Dropdown<DropdownStateType, TT>
263
+ const dropdown = this.app.makeOverlay(constructor, state, 'dropdown') as Dropdown<DropdownStateType, TThemeType>
258
264
  dropdown.parentPart = this
259
265
  dropdown.anchor(target)
260
266
  this.app.lastDropdownTarget = target
@@ -270,7 +276,7 @@ export abstract class ContentPart<T, TT extends ThemeType> extends TerrierPart<T
270
276
  * @param state the dropdown's state
271
277
  * @param target the target element around which to show the dropdown
272
278
  */
273
- toggleDropdown<DropdownType extends Dropdown<DropdownStateType, TT>, DropdownStateType>(
279
+ toggleDropdown<DropdownType extends Dropdown<DropdownStateType, TThemeType>, DropdownStateType>(
274
280
  constructor: { new(p: PartParent, id: string, state: DropdownStateType): DropdownType; },
275
281
  state: DropdownStateType,
276
282
  target: EventTarget | null) {
@@ -335,50 +341,52 @@ export abstract class PagePart<T, TT extends ThemeType> extends ContentPart<T, T
335
341
  render(parent: PartTag) {
336
342
  parent.div(`.tt-page-part.content-width-${this.mainContentWidth}`, page => {
337
343
  page.div('.tt-flex.top-row', topRow => {
338
- // breadcrumbs
339
- if (this._breadcrumbs.length || this._title?.length) {
340
- topRow.h1('.breadcrumbs', h1 => {
341
- const crumbs = Array.from(this._breadcrumbs)
342
-
343
- // add a breadcrumb for the page title
344
- const titleCrumb: Action<TT> = {
345
- title: this._title,
346
- icon: this._icon || undefined,
347
- }
348
- if (this._titleHref) {
349
- titleCrumb.href = this._titleHref
350
- }
351
- if (this._breadcrumbClasses?.length) {
352
- titleCrumb.classes = this._breadcrumbClasses
353
- }
354
- crumbs.push(titleCrumb)
344
+ this.renderBreadcrumbs(topRow);
355
345
 
356
- this.app.theme.renderActions(h1, crumbs)
357
- })
358
- }
359
-
360
- // tertiary actions
361
346
  if (this.actions.tertiary.length) {
362
- topRow.div('.tertiary-actions', actions => {
363
- this.app.theme.renderActions(actions, this.getActions('tertiary'))
364
- })
347
+ this.renderActions(topRow, 'tertiary');
365
348
  }
366
- }) // topRow
349
+ })
367
350
 
368
351
  page.div('.lighting')
369
352
  page.div('.page-main', main => {
370
353
  this.renderContent(main)
371
354
  main.div('.page-actions', actions => {
372
- actions.div('.secondary-actions', container => {
373
- this.app.theme.renderActions(container, this.getActions('secondary'), {iconColor: 'white', defaultClass: 'secondary'})
374
- })
375
- actions.div('.primary-actions', container => {
376
- this.app.theme.renderActions(container, this.getActions('primary'), {iconColor: 'white', defaultClass: 'primary'})
377
- })
355
+ this.renderActions(actions, 'secondary', {iconColor: null, defaultClass: 'secondary'})
356
+ this.renderActions(actions, 'primary', {iconColor: null, defaultClass: 'primary'})
378
357
  })
379
358
  })
380
359
  })
381
360
  }
361
+
362
+ protected renderActions(parent: PartTag, level: ActionLevel, options?: RenderActionOptions<TT>) {
363
+ parent.div(`.${level}-actions`, actions => {
364
+ this.app.theme.renderActions(actions, this.getActions(level), options)
365
+ })
366
+ }
367
+
368
+ protected renderBreadcrumbs(parent: PartTag) {
369
+ if (!this._breadcrumbs.length && !this._title?.length) return
370
+
371
+ parent.h1('.breadcrumbs', h1 => {
372
+ const crumbs = Array.from(this._breadcrumbs)
373
+
374
+ // add a breadcrumb for the page title
375
+ const titleCrumb: Action<TT> = {
376
+ title: this._title,
377
+ icon: this._icon || undefined,
378
+ }
379
+ if (this._titleHref) {
380
+ titleCrumb.href = this._titleHref
381
+ }
382
+ if (this._breadcrumbClasses?.length) {
383
+ titleCrumb.classes = this._breadcrumbClasses
384
+ }
385
+ crumbs.push(titleCrumb)
386
+
387
+ this.app.theme.renderActions(h1, crumbs)
388
+ })
389
+ }
382
390
  }
383
391
 
384
392