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 +15 -7
- package/dropdowns.ts +3 -2
- package/lightbox.ts +5 -3
- package/modals.ts +3 -2
- package/package.json +2 -2
- package/parts.ts +65 -57
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<
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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,
|
|
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<
|
|
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<
|
|
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<
|
|
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<
|
|
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.
|
|
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
|
|
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<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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():
|
|
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<
|
|
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<
|
|
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:
|
|
147
|
+
protected _icon: TThemeType['icons'] | null = null
|
|
142
148
|
|
|
143
|
-
setIcon(icon:
|
|
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<
|
|
159
|
-
secondary: Array<Action<
|
|
160
|
-
tertiary: Array<Action<
|
|
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<
|
|
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<
|
|
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<
|
|
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<
|
|
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<
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
363
|
-
this.app.theme.renderActions(actions, this.getActions('tertiary'))
|
|
364
|
-
})
|
|
347
|
+
this.renderActions(topRow, 'tertiary');
|
|
365
348
|
}
|
|
366
|
-
})
|
|
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
|
-
|
|
373
|
-
|
|
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
|
|