terrier-engine 4.8.11 → 4.9.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/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "files": [
5
5
  "*"
6
6
  ],
7
- "version": "4.8.11",
7
+ "version": "4.9.0",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "https://github.com/Terrier-Tech/terrier-engine"
@@ -0,0 +1,146 @@
1
+ import TerrierPart from "./parts/terrier-part"
2
+ import {PartTag} from "tuff-core/parts"
3
+ import Messages from "tuff-core/messages"
4
+ import {Logger} from "tuff-core/logging"
5
+ import Html from "tuff-core/html"
6
+
7
+ const log = new Logger('List Viewer')
8
+
9
+ const detailsSelector = '.tt-list-viewer-details'
10
+
11
+ /**
12
+ * Part for viewing a list of items and the details associated with them.
13
+ * Each item must have an `id` so that they can be distinguished.
14
+ */
15
+ export abstract class ListViewerPart<T extends {id: string}> extends TerrierPart<any> {
16
+
17
+ items: T[] = []
18
+ itemMap: Record<string, T> = {}
19
+
20
+ itemClickedKey = Messages.typedKey<{id: string}>()
21
+
22
+ /**
23
+ * A message with this key gets emitted whenever the details are shown.
24
+ */
25
+ detailsShownKey = Messages.typedKey<{ id: string }>()
26
+
27
+ async init() {
28
+ await super.init()
29
+
30
+ await this.reload()
31
+
32
+ this.onClick(this.itemClickedKey, m => {
33
+ log.info(`Clicked on list item ${m.data.id}`, m)
34
+ this.showDetails(m.data.id)
35
+ })
36
+ }
37
+
38
+
39
+ /// Fetching
40
+
41
+ abstract fetchItems(): Promise<T[]>
42
+
43
+ async reload() {
44
+ this.items = await this.fetchItems()
45
+ this.items.forEach((item) => {
46
+ this.itemMap[item.id] = item
47
+ })
48
+ this.dirty()
49
+ }
50
+
51
+
52
+ /// Rendering
53
+
54
+ get parentClasses(): Array<string> {
55
+ return ['tt-list-viewer']
56
+ }
57
+
58
+ render(parent: PartTag): any {
59
+ parent.div('.tt-list-viewer-list', list => {
60
+ for (const item of this.items) {
61
+ list.a('.tt-list-viewer-item', {id: `item-${item.id}`}, itemView => {
62
+ this.renderListItem(itemView, item)
63
+ }).emitClick(this.itemClickedKey, {id: item.id})
64
+ }
65
+ })
66
+ parent.div('.tt-list-viewer-details-container', detailsView => {
67
+ detailsView.div(detailsSelector)
68
+ })
69
+ }
70
+ abstract renderListItem(parent: PartTag, item: T): any
71
+
72
+ abstract renderItemDetail(parent: PartTag, item: T): any
73
+
74
+
75
+ // Details
76
+
77
+ private setCurrent(id: string) {
78
+ // clear any existing current item
79
+ const existingCurrents = this.element!.querySelectorAll('.tt-list-viewer-list a.current')
80
+ existingCurrents.forEach((elem) => {
81
+ elem.classList.remove('current')
82
+ })
83
+
84
+ // add .current to the new item
85
+ const itemView = this.element!.querySelector(`#item-${id}`)
86
+ if (itemView) {
87
+ itemView.classList.add('current')
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Show the details view for the item with the given id
93
+ * @param id
94
+ */
95
+ showDetails(id: string) {
96
+ this.setCurrent(id)
97
+ const item = this.itemMap[id]
98
+ if (!item) {
99
+ throw `No item ${id}`
100
+ }
101
+ const container = this.element!.querySelector(detailsSelector)
102
+ if (container) {
103
+ // render the details
104
+ const detailsView = Html.createElement('div', div => {
105
+ this.renderItemDetail(div, item)
106
+ })
107
+ container.innerHTML = detailsView.innerHTML
108
+ this.arrangeDetails(id, container as HTMLElement)
109
+
110
+ // let the world know
111
+ this.emitMessage(this.detailsShownKey, {id})
112
+ }
113
+ else {
114
+ log.warn(`Tried to show item ${id} but there was no ${detailsSelector}`)
115
+ }
116
+ }
117
+
118
+ /**
119
+ * If necessary, move the details next to the item
120
+ * @param id the item id
121
+ * @param detailsView
122
+ */
123
+ arrangeDetails(id: string, detailsView: HTMLElement) {
124
+ const itemView = this.element!.querySelector(`#item-${id}`)
125
+ if (itemView) {
126
+ // const listView = itemIVew.parentElement
127
+ log.info(`Item is ${itemView.clientWidth} wide and the window is ${window.innerWidth} wide`)
128
+ // crude but effective way to determine if the list is collapsed due to the media breakpoint
129
+ if (itemView.clientWidth > window.innerWidth * 0.8) {
130
+ // move the details to right after the list item
131
+ itemView.after(detailsView)
132
+ }
133
+ else {
134
+ // move the details back to the container
135
+ const detailsContainer = this.element!.querySelector(`.tt-list-viewer-details-container`)
136
+ if (detailsContainer) {
137
+ detailsContainer.append(detailsView)
138
+ }
139
+ }
140
+ }
141
+ else {
142
+ log.warn(`No item view for ${id}`)
143
+ }
144
+ }
145
+
146
+ }