@internetarchive/collection-browser 0.0.1-alpha.1

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.
Files changed (203) hide show
  1. package/.editorconfig +29 -0
  2. package/.github/workflows/ci.yml +26 -0
  3. package/.husky/pre-commit +4 -0
  4. package/LICENSE +661 -0
  5. package/README.md +68 -0
  6. package/demo/app-root.ts +414 -0
  7. package/demo/index.html +26 -0
  8. package/dist/demo/app-root.d.ts +43 -0
  9. package/dist/demo/app-root.js +385 -0
  10. package/dist/demo/app-root.js.map +1 -0
  11. package/dist/index.d.ts +3 -0
  12. package/dist/index.js +3 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/src/assets/img/icons/audio.d.ts +1 -0
  15. package/dist/src/assets/img/icons/audio.js +9 -0
  16. package/dist/src/assets/img/icons/audio.js.map +1 -0
  17. package/dist/src/assets/img/icons/collection.d.ts +1 -0
  18. package/dist/src/assets/img/icons/collection.js +9 -0
  19. package/dist/src/assets/img/icons/collection.js.map +1 -0
  20. package/dist/src/assets/img/icons/etree.d.ts +1 -0
  21. package/dist/src/assets/img/icons/etree.js +9 -0
  22. package/dist/src/assets/img/icons/etree.js.map +1 -0
  23. package/dist/src/assets/img/icons/images.d.ts +1 -0
  24. package/dist/src/assets/img/icons/images.js +10 -0
  25. package/dist/src/assets/img/icons/images.js.map +1 -0
  26. package/dist/src/assets/img/icons/mediatype/account.d.ts +2 -0
  27. package/dist/src/assets/img/icons/mediatype/account.js +12 -0
  28. package/dist/src/assets/img/icons/mediatype/account.js.map +1 -0
  29. package/dist/src/assets/img/icons/mediatype/audio.d.ts +1 -0
  30. package/dist/src/assets/img/icons/mediatype/audio.js +11 -0
  31. package/dist/src/assets/img/icons/mediatype/audio.js.map +1 -0
  32. package/dist/src/assets/img/icons/mediatype/collection.d.ts +1 -0
  33. package/dist/src/assets/img/icons/mediatype/collection.js +9 -0
  34. package/dist/src/assets/img/icons/mediatype/collection.js.map +1 -0
  35. package/dist/src/assets/img/icons/mediatype/etree copy.d.ts +1 -0
  36. package/dist/src/assets/img/icons/mediatype/etree copy.js +9 -0
  37. package/dist/src/assets/img/icons/mediatype/etree copy.js.map +1 -0
  38. package/dist/src/assets/img/icons/mediatype/etree.d.ts +1 -0
  39. package/dist/src/assets/img/icons/mediatype/etree.js +9 -0
  40. package/dist/src/assets/img/icons/mediatype/etree.js.map +1 -0
  41. package/dist/src/assets/img/icons/mediatype/film.d.ts +1 -0
  42. package/dist/src/assets/img/icons/mediatype/film.js +13 -0
  43. package/dist/src/assets/img/icons/mediatype/film.js.map +1 -0
  44. package/dist/src/assets/img/icons/mediatype/images.d.ts +1 -0
  45. package/dist/src/assets/img/icons/mediatype/images.js +10 -0
  46. package/dist/src/assets/img/icons/mediatype/images.js.map +1 -0
  47. package/dist/src/assets/img/icons/mediatype/livemusic.d.ts +1 -0
  48. package/dist/src/assets/img/icons/mediatype/livemusic.js +7 -0
  49. package/dist/src/assets/img/icons/mediatype/livemusic.js.map +1 -0
  50. package/dist/src/assets/img/icons/mediatype/photos.d.ts +1 -0
  51. package/dist/src/assets/img/icons/mediatype/photos.js +7 -0
  52. package/dist/src/assets/img/icons/mediatype/photos.js.map +1 -0
  53. package/dist/src/assets/img/icons/mediatype/software.d.ts +1 -0
  54. package/dist/src/assets/img/icons/mediatype/software.js +10 -0
  55. package/dist/src/assets/img/icons/mediatype/software.js.map +1 -0
  56. package/dist/src/assets/img/icons/mediatype/texts.d.ts +1 -0
  57. package/dist/src/assets/img/icons/mediatype/texts.js +10 -0
  58. package/dist/src/assets/img/icons/mediatype/texts.js.map +1 -0
  59. package/dist/src/assets/img/icons/mediatype/tv.d.ts +1 -0
  60. package/dist/src/assets/img/icons/mediatype/tv.js +9 -0
  61. package/dist/src/assets/img/icons/mediatype/tv.js.map +1 -0
  62. package/dist/src/assets/img/icons/mediatype/video.d.ts +1 -0
  63. package/dist/src/assets/img/icons/mediatype/video.js +10 -0
  64. package/dist/src/assets/img/icons/mediatype/video.js.map +1 -0
  65. package/dist/src/assets/img/icons/mediatype/web.d.ts +1 -0
  66. package/dist/src/assets/img/icons/mediatype/web.js +10 -0
  67. package/dist/src/assets/img/icons/mediatype/web.js.map +1 -0
  68. package/dist/src/assets/img/icons/software.d.ts +1 -0
  69. package/dist/src/assets/img/icons/software.js +10 -0
  70. package/dist/src/assets/img/icons/software.js.map +1 -0
  71. package/dist/src/assets/img/icons/texts.d.ts +1 -0
  72. package/dist/src/assets/img/icons/texts.js +10 -0
  73. package/dist/src/assets/img/icons/texts.js.map +1 -0
  74. package/dist/src/assets/img/icons/tv.d.ts +1 -0
  75. package/dist/src/assets/img/icons/tv.js +9 -0
  76. package/dist/src/assets/img/icons/tv.js.map +1 -0
  77. package/dist/src/assets/img/icons/video.d.ts +1 -0
  78. package/dist/src/assets/img/icons/video.js +10 -0
  79. package/dist/src/assets/img/icons/video.js.map +1 -0
  80. package/dist/src/assets/img/icons/web.d.ts +1 -0
  81. package/dist/src/assets/img/icons/web.js +10 -0
  82. package/dist/src/assets/img/icons/web.js.map +1 -0
  83. package/dist/src/circular-activity-indicator.d.ts +5 -0
  84. package/dist/src/circular-activity-indicator.js +66 -0
  85. package/dist/src/circular-activity-indicator.js.map +1 -0
  86. package/dist/src/collection-browser.d.ts +151 -0
  87. package/dist/src/collection-browser.js +697 -0
  88. package/dist/src/collection-browser.js.map +1 -0
  89. package/dist/src/collection-facets.d.ts +34 -0
  90. package/dist/src/collection-facets.js +245 -0
  91. package/dist/src/collection-facets.js.map +1 -0
  92. package/dist/src/mediatype-icon.d.ts +9 -0
  93. package/dist/src/mediatype-icon.js +89 -0
  94. package/dist/src/mediatype-icon.js.map +1 -0
  95. package/dist/src/models.d.ts +23 -0
  96. package/dist/src/models.js +2 -0
  97. package/dist/src/models.js.map +1 -0
  98. package/dist/src/sort-filter-bar/alpha-bar.d.ts +10 -0
  99. package/dist/src/sort-filter-bar/alpha-bar.js +88 -0
  100. package/dist/src/sort-filter-bar/alpha-bar.js.map +1 -0
  101. package/dist/src/sort-filter-bar/sort-filter-bar.d.ts +24 -0
  102. package/dist/src/sort-filter-bar/sort-filter-bar.js +257 -0
  103. package/dist/src/sort-filter-bar/sort-filter-bar.js.map +1 -0
  104. package/dist/src/tiles/grid/account-tile.d.ts +7 -0
  105. package/dist/src/tiles/grid/account-tile.js +144 -0
  106. package/dist/src/tiles/grid/account-tile.js.map +1 -0
  107. package/dist/src/tiles/grid/collection-tile.d.ts +7 -0
  108. package/dist/src/tiles/grid/collection-tile.js +160 -0
  109. package/dist/src/tiles/grid/collection-tile.js.map +1 -0
  110. package/dist/src/tiles/grid/icons/account.d.ts +1 -0
  111. package/dist/src/tiles/grid/icons/account.js +12 -0
  112. package/dist/src/tiles/grid/icons/account.js.map +1 -0
  113. package/dist/src/tiles/grid/icons/favorite-filled.d.ts +1 -0
  114. package/dist/src/tiles/grid/icons/favorite-filled.js +11 -0
  115. package/dist/src/tiles/grid/icons/favorite-filled.js.map +1 -0
  116. package/dist/src/tiles/grid/icons/reviews.d.ts +1 -0
  117. package/dist/src/tiles/grid/icons/reviews.js +11 -0
  118. package/dist/src/tiles/grid/icons/reviews.js.map +1 -0
  119. package/dist/src/tiles/grid/icons/upload.d.ts +1 -0
  120. package/dist/src/tiles/grid/icons/upload.js +12 -0
  121. package/dist/src/tiles/grid/icons/upload.js.map +1 -0
  122. package/dist/src/tiles/grid/icons/views.d.ts +2 -0
  123. package/dist/src/tiles/grid/icons/views.js +11 -0
  124. package/dist/src/tiles/grid/icons/views.js.map +1 -0
  125. package/dist/src/tiles/grid/item-tile.d.ts +9 -0
  126. package/dist/src/tiles/grid/item-tile.js +240 -0
  127. package/dist/src/tiles/grid/item-tile.js.map +1 -0
  128. package/dist/src/tiles/list/tile-list-compact.d.ts +16 -0
  129. package/dist/src/tiles/list/tile-list-compact.js +125 -0
  130. package/dist/src/tiles/list/tile-list-compact.js.map +1 -0
  131. package/dist/src/tiles/list/tile-list-detail.d.ts +17 -0
  132. package/dist/src/tiles/list/tile-list-detail.js +181 -0
  133. package/dist/src/tiles/list/tile-list-detail.js.map +1 -0
  134. package/dist/src/tiles/list/tile-list.d.ts +21 -0
  135. package/dist/src/tiles/list/tile-list.js +229 -0
  136. package/dist/src/tiles/list/tile-list.js.map +1 -0
  137. package/dist/src/tiles/loading-tile.d.ts +5 -0
  138. package/dist/src/tiles/loading-tile.js +73 -0
  139. package/dist/src/tiles/loading-tile.js.map +1 -0
  140. package/dist/src/tiles/tile-dispatcher.d.ts +27 -0
  141. package/dist/src/tiles/tile-dispatcher.js +160 -0
  142. package/dist/src/tiles/tile-dispatcher.js.map +1 -0
  143. package/dist/src/utils/format-count.d.ts +7 -0
  144. package/dist/src/utils/format-count.js +76 -0
  145. package/dist/src/utils/format-count.js.map +1 -0
  146. package/dist/src/utils/format-date.d.ts +2 -0
  147. package/dist/src/utils/format-date.js +24 -0
  148. package/dist/src/utils/format-date.js.map +1 -0
  149. package/dist/src/utils/format-string.d.ts +2 -0
  150. package/dist/src/utils/format-string.js +7 -0
  151. package/dist/src/utils/format-string.js.map +1 -0
  152. package/dist/test/collection-browser.test.d.ts +0 -0
  153. package/dist/test/collection-browser.test.js +3 -0
  154. package/dist/test/collection-browser.test.js.map +1 -0
  155. package/dist/test/utils/format-count.test.d.ts +1 -0
  156. package/dist/test/utils/format-count.test.js +24 -0
  157. package/dist/test/utils/format-count.test.js.map +1 -0
  158. package/dist/test/utils/format-date.test.d.ts +1 -0
  159. package/dist/test/utils/format-date.test.js +18 -0
  160. package/dist/test/utils/format-date.test.js.map +1 -0
  161. package/dist/test/utils/format-string.test.d.ts +1 -0
  162. package/dist/test/utils/format-string.test.js +17 -0
  163. package/dist/test/utils/format-string.test.js.map +1 -0
  164. package/index.ts +3 -0
  165. package/package.json +95 -0
  166. package/src/assets/img/icons/mediatype/account.ts +12 -0
  167. package/src/assets/img/icons/mediatype/audio.ts +11 -0
  168. package/src/assets/img/icons/mediatype/collection.ts +9 -0
  169. package/src/assets/img/icons/mediatype/etree.ts +9 -0
  170. package/src/assets/img/icons/mediatype/film.ts +13 -0
  171. package/src/assets/img/icons/mediatype/foo.svg +5 -0
  172. package/src/assets/img/icons/mediatype/images.ts +10 -0
  173. package/src/assets/img/icons/mediatype/software.ts +10 -0
  174. package/src/assets/img/icons/mediatype/texts.ts +10 -0
  175. package/src/assets/img/icons/mediatype/tv.ts +9 -0
  176. package/src/assets/img/icons/mediatype/video.ts +10 -0
  177. package/src/assets/img/icons/mediatype/web.ts +10 -0
  178. package/src/circular-activity-indicator.ts +64 -0
  179. package/src/collection-browser.ts +756 -0
  180. package/src/collection-facets.ts +285 -0
  181. package/src/mediatype-icon.ts +83 -0
  182. package/src/models.ts +25 -0
  183. package/src/sort-filter-bar/alpha-bar.ts +86 -0
  184. package/src/sort-filter-bar/sort-filter-bar.ts +256 -0
  185. package/src/tiles/grid/account-tile.ts +141 -0
  186. package/src/tiles/grid/collection-tile.ts +157 -0
  187. package/src/tiles/grid/icons/account.ts +12 -0
  188. package/src/tiles/grid/icons/favorite-filled.ts +11 -0
  189. package/src/tiles/grid/icons/reviews.ts +11 -0
  190. package/src/tiles/grid/icons/upload.ts +12 -0
  191. package/src/tiles/grid/icons/views.ts +11 -0
  192. package/src/tiles/grid/item-tile.ts +254 -0
  193. package/src/tiles/list/tile-list.ts +227 -0
  194. package/src/tiles/loading-tile.ts +70 -0
  195. package/src/tiles/tile-dispatcher.ts +160 -0
  196. package/src/utils/format-count.ts +95 -0
  197. package/src/utils/format-date.ts +36 -0
  198. package/test/collection-browser.test.ts +1 -0
  199. package/test/utils/format-count.test.ts +32 -0
  200. package/test/utils/format-date.test.ts +22 -0
  201. package/tsconfig.json +20 -0
  202. package/web-dev-server.config.mjs +28 -0
  203. package/web-test-runner.config.mjs +41 -0
@@ -0,0 +1,285 @@
1
+ import { css, html, LitElement } from 'lit';
2
+ import { customElement, property } from 'lit/decorators.js';
3
+ import { repeat } from 'lit/directives/repeat.js';
4
+ import { Aggregation } from '@internetarchive/search-service';
5
+
6
+ type FacetOption =
7
+ | 'subject'
8
+ | 'mediatype'
9
+ | 'language'
10
+ | 'creator'
11
+ | 'collection'
12
+ | 'year';
13
+
14
+ const facetDisplayOrder: FacetOption[] = [
15
+ 'mediatype',
16
+ 'year',
17
+ 'subject',
18
+ 'collection',
19
+ 'creator',
20
+ 'language',
21
+ ];
22
+
23
+ const aggregationToFacetOption: Record<string, FacetOption> = {
24
+ subjectSorter: 'subject',
25
+ mediatypeSorter: 'mediatype',
26
+ languageSorter: 'language',
27
+ creatorSorter: 'creator',
28
+ collection: 'collection',
29
+ year: 'year',
30
+ };
31
+
32
+ const facetTitles: Record<FacetOption, string> = {
33
+ subject: 'Subject',
34
+ mediatype: 'Media Type',
35
+ language: 'Language',
36
+ creator: 'Creator',
37
+ collection: 'Collection',
38
+ year: 'Year',
39
+ };
40
+
41
+ interface FacetBucket {
42
+ // for some facets, we augment the key with a display value
43
+ displayText?: string;
44
+ key: string;
45
+ count: number;
46
+ selected: boolean;
47
+ }
48
+
49
+ interface FacetGroup {
50
+ title: string;
51
+ key: string;
52
+ buckets: FacetBucket[];
53
+ }
54
+
55
+ @customElement('collection-facets')
56
+ export class CollectionFacets extends LitElement {
57
+ @property({ type: Object }) aggregations?: Record<string, Aggregation>;
58
+
59
+ @property({ type: Object }) selectedFacets: Record<string, string[]> = {};
60
+
61
+ private get hydratedSelectedFacets(): Record<string, string[]> {
62
+ const { selectedFacets } = this;
63
+ const hydratedSelectedFacets: Record<string, string[]> = {};
64
+ Object.entries(selectedFacets).forEach(([key]) => {
65
+ const values = hydratedSelectedFacets[key];
66
+ const title = facetTitles[key as FacetOption];
67
+ hydratedSelectedFacets[title] = values || [];
68
+ delete hydratedSelectedFacets[key];
69
+ });
70
+ return hydratedSelectedFacets;
71
+ }
72
+
73
+ render() {
74
+ return html`
75
+ <h1>Facets</h1>
76
+
77
+ ${this.mergedFacets.map(facetGroup => this.getFacetTemplate(facetGroup))}
78
+ `;
79
+ }
80
+
81
+ /**
82
+ * Combines the selected facets with the aggregations to create a single list of facets
83
+ */
84
+ private get mergedFacets(): FacetGroup[] {
85
+ const facetGroups: FacetGroup[] = [];
86
+
87
+ facetDisplayOrder.forEach(facetKey => {
88
+ const selectedFacetGroup = this.selectedFacetGroups.find(
89
+ group => group.key === facetKey
90
+ );
91
+ const aggregateFacetGroup = this.aggregationFacetGroups.find(
92
+ group => group.key === facetKey
93
+ );
94
+
95
+ // if the user selected a facet, but it's not in the aggregation, we add it as-is
96
+ if (selectedFacetGroup && !aggregateFacetGroup) {
97
+ facetGroups.push(selectedFacetGroup);
98
+ return;
99
+ }
100
+
101
+ // if we don't have an aggregate facet group, don't add this to the list
102
+ if (!aggregateFacetGroup) return;
103
+
104
+ // start with either the selected group if we have one, or the aggregate group
105
+ const facetGroup = selectedFacetGroup ?? aggregateFacetGroup;
106
+
107
+ // attach the counts to the selected buckets
108
+ const bucketsWithCount =
109
+ selectedFacetGroup?.buckets.map(bucket => {
110
+ const selectedBucket = aggregateFacetGroup.buckets.find(
111
+ b => b.key === bucket.key
112
+ );
113
+ if (selectedBucket) {
114
+ return {
115
+ ...bucket,
116
+ count: selectedBucket.count,
117
+ };
118
+ }
119
+ return bucket;
120
+ }) ?? [];
121
+
122
+ // append any additional buckets that were not selected
123
+ aggregateFacetGroup.buckets.forEach(bucket => {
124
+ const existingBucket = bucketsWithCount.find(b => b.key === bucket.key);
125
+ if (existingBucket) return;
126
+ bucketsWithCount.push(bucket);
127
+ });
128
+ facetGroup.buckets = bucketsWithCount.splice(0, 5);
129
+
130
+ if (facetGroup.buckets.length === 0) return;
131
+
132
+ facetGroups.push(facetGroup);
133
+ });
134
+
135
+ return facetGroups;
136
+ }
137
+
138
+ /**
139
+ * Converts the raw `selectedFacets` to `FacetGroups`, which are easier to use
140
+ */
141
+ private get selectedFacetGroups(): FacetGroup[] {
142
+ const selectedFacetGroups: FacetGroup[] = [];
143
+ Object.entries(this.selectedFacets).forEach(([key, buckets]) => {
144
+ const title = facetTitles[key as FacetOption];
145
+ const group = {
146
+ title,
147
+ key,
148
+ buckets: buckets.map(bucket => ({
149
+ key: bucket,
150
+ count: 0,
151
+ selected: true,
152
+ })),
153
+ };
154
+ selectedFacetGroups.push(group);
155
+ });
156
+ return selectedFacetGroups;
157
+ }
158
+
159
+ /**
160
+ * Converts the raw `aggregations` to `FacetGroups`, which are easier to use
161
+ */
162
+ private get aggregationFacetGroups(): FacetGroup[] {
163
+ const facetGroups: FacetGroup[] = [];
164
+ Object.entries(this.aggregations ?? []).forEach(([key, buckets]) => {
165
+ if (key === 'year_histogram') return;
166
+ const option = this.getFacetOptionFromKey(key);
167
+ const title = facetTitles[option];
168
+ const facetBuckets: FacetBucket[] = buckets.buckets.map(bucket => ({
169
+ key: `${bucket.key}`,
170
+ count: bucket.doc_count,
171
+ selected: false,
172
+ }));
173
+ const group: FacetGroup = {
174
+ title,
175
+ key: option,
176
+ buckets: facetBuckets,
177
+ };
178
+ facetGroups.push(group);
179
+ });
180
+ return facetGroups;
181
+ }
182
+
183
+ private getFacetTemplate(facetGroup: FacetGroup) {
184
+ return html`
185
+ <h2>${facetGroup.title}</h2>
186
+ ${repeat(
187
+ facetGroup.buckets,
188
+ bucket => `${facetGroup.key}:${bucket.key}`,
189
+ bucket => html`
190
+ <label class="facet-row">
191
+ <div class="facet-checkbox">
192
+ <input
193
+ type="checkbox"
194
+ .name=${facetGroup.key}
195
+ .value=${bucket.key}
196
+ @click=${this.facetToggled}
197
+ ?checked=${bucket.selected}
198
+ />
199
+ </div>
200
+ <div class="facet-title">${bucket.key}</div>
201
+ <div class="facet-count">${bucket.count}</div>
202
+ </label>
203
+ `
204
+ )}
205
+ `;
206
+ }
207
+
208
+ private facetChecked(name: string, value: string) {
209
+ const { selectedFacets } = this;
210
+ const facetClone = { ...selectedFacets };
211
+ const currentFacetValues = facetClone[name];
212
+ if (currentFacetValues) {
213
+ currentFacetValues.push(value);
214
+ facetClone[name] = currentFacetValues;
215
+ } else {
216
+ facetClone[name] = [value];
217
+ }
218
+ this.selectedFacets = facetClone;
219
+ }
220
+
221
+ private facetUnchecked(name: string, value: string) {
222
+ const { selectedFacets } = this;
223
+ const facetClone = { ...selectedFacets };
224
+ let currentFacetValues = selectedFacets[name];
225
+ if (currentFacetValues) {
226
+ currentFacetValues = currentFacetValues.filter(el => el !== value);
227
+ facetClone[name] = currentFacetValues;
228
+ if (currentFacetValues.length === 0) {
229
+ delete facetClone[name];
230
+ }
231
+ }
232
+ this.selectedFacets = facetClone;
233
+ }
234
+
235
+ private facetToggled(e: Event) {
236
+ const target = e.target as HTMLInputElement;
237
+ const { checked, name, value } = target;
238
+ if (checked) {
239
+ this.facetChecked(name, value);
240
+ } else {
241
+ this.facetUnchecked(name, value);
242
+ }
243
+
244
+ const event = new CustomEvent<Record<string, string[]>>('facetsChanged', {
245
+ detail: this.selectedFacets,
246
+ });
247
+ this.dispatchEvent(event);
248
+ }
249
+
250
+ /**
251
+ * Parse the aggregate key title into the human readable title
252
+ *
253
+ * Example: user_aggs__terms__field:mediatypeSorter__size:6 => Media Type
254
+ *
255
+ * @param key
256
+ * @returns
257
+ */
258
+ private getFacetOptionFromKey(key: string): FacetOption {
259
+ const parts = key.split('__');
260
+ const fieldNamePart = parts[2];
261
+ const fieldName = fieldNamePart.split(':')[1];
262
+ const facetMatch = Object.entries(aggregationToFacetOption).find(([key2]) =>
263
+ fieldName.includes(key2)
264
+ );
265
+ const option = facetMatch?.[1];
266
+ if (!option) throw new Error(`Could not find facet option for key: ${key}`);
267
+ return option;
268
+ }
269
+
270
+ static get styles() {
271
+ return css`
272
+ .facet-row {
273
+ display: flex;
274
+ }
275
+
276
+ .facet-title {
277
+ flex: 1;
278
+ }
279
+
280
+ .facet-count {
281
+ margin-left: 0.5rem;
282
+ }
283
+ `;
284
+ }
285
+ }
@@ -0,0 +1,83 @@
1
+ import { css, CSSResultGroup, html, LitElement } from 'lit';
2
+ import { customElement, property } from 'lit/decorators.js';
3
+
4
+ import { audioIcon } from './assets/img/icons/mediatype/audio';
5
+ import { etreeIcon } from './assets/img/icons/mediatype/etree';
6
+ import { imagesIcon } from './assets/img/icons/mediatype/images';
7
+ import { filmIcon } from './assets/img/icons/mediatype/film';
8
+ import { softwareIcon } from './assets/img/icons/mediatype/software';
9
+ import { textsIcon } from './assets/img/icons/mediatype/texts';
10
+ import { tvIcon } from './assets/img/icons/mediatype/tv';
11
+ import { videoIcon } from './assets/img/icons/mediatype/video';
12
+ import { webIcon } from './assets/img/icons/mediatype/web';
13
+ import { collectionIcon } from './assets/img/icons/mediatype/collection';
14
+
15
+ @customElement('mediatype-icon')
16
+ export class MediatypeIcon extends LitElement {
17
+ @property({ type: String }) mediatype: string | undefined;
18
+
19
+ @property({ type: Boolean }) showText = false;
20
+
21
+ private readonly mediatypeIcons: { [key: string]: any } = {
22
+ audio: audioIcon,
23
+ collection: collectionIcon,
24
+ data: etreeIcon,
25
+ etree: etreeIcon,
26
+ film: filmIcon,
27
+ image: imagesIcon,
28
+ movies: filmIcon,
29
+ software: softwareIcon,
30
+ texts: textsIcon,
31
+ tv: tvIcon,
32
+ video: videoIcon,
33
+ web: webIcon,
34
+ };
35
+
36
+ private readonly mediatypeText: { [key: string]: any } = {
37
+ audio: 'Audio',
38
+ collection: 'Collection',
39
+ data: 'Data',
40
+ etree: 'E-tree',
41
+ film: 'Film',
42
+ image: 'Image',
43
+ movies: 'Movie',
44
+ software: 'Software',
45
+ texts: 'Text',
46
+ tv: 'TV',
47
+ video: 'Video',
48
+ web: 'Web',
49
+ };
50
+
51
+ render() {
52
+ if (!this.mediatype) {
53
+ return html``;
54
+ }
55
+ return html`
56
+ <div id="icon" class="${this.showText ? 'show-text' : 'hide-text'}">
57
+ ${this.mediatypeIcons[this.mediatype]}
58
+ <p class="status-text">${this.mediatypeText[this.mediatype]}</p>
59
+ </div>
60
+ `;
61
+ }
62
+
63
+ static get styles(): CSSResultGroup {
64
+ return css`
65
+ .status-text {
66
+ font-size: 14px;
67
+ color: #2c2c2c;
68
+ margin: auto;
69
+ display: block;
70
+ text-align: center;
71
+ }
72
+
73
+ #icon.hide-text p {
74
+ display: none;
75
+ }
76
+
77
+ #icon.show-text svg {
78
+ height: 10px;
79
+ width: 10px;
80
+ }
81
+ `;
82
+ }
83
+ }
package/src/models.ts ADDED
@@ -0,0 +1,25 @@
1
+ import type { MediaType } from '@internetarchive/field-parsers';
2
+
3
+ export interface TileModel {
4
+ identifier: string;
5
+ title: string;
6
+ averageRating?: number;
7
+ dateAdded?: Date; // Date added to public search (software-defined) [from: addeddate]
8
+ dateArchived?: Date; // Date archived (software-defined) item created on archive.org [from: publicdate]
9
+ dateReviewed?: Date; // Date reviewed (user-created) most recent review [from: reviewdate]
10
+ datePublished?: Date; // Date work published in the world (user-defined) [from: date]
11
+ mediatype: MediaType;
12
+ viewCount: number;
13
+ itemCount: number;
14
+ favCount: number;
15
+ commentCount: number;
16
+ description?: string;
17
+ collectionIdentifier?: string;
18
+ collectionName?: string;
19
+ creator?: string;
20
+ subject?: string;
21
+ source?: string;
22
+ collection: string[];
23
+ }
24
+
25
+ export type CollectionDisplayMode = 'grid' | 'list-compact' | 'list-detail';
@@ -0,0 +1,86 @@
1
+ import { LitElement, html, css, nothing } from 'lit';
2
+ import { customElement, property } from 'lit/decorators.js';
3
+
4
+ @customElement('alpha-bar')
5
+ export class AlphaBar extends LitElement {
6
+ @property({ type: String }) headline?: string;
7
+
8
+ @property({ type: String }) selectedLetter?: string;
9
+
10
+ private get selectedUppercaseLetter(): string | undefined {
11
+ return this.selectedLetter?.toUpperCase();
12
+ }
13
+
14
+ render() {
15
+ return html`
16
+ <h1>${this.headline}</h1>
17
+ <div id="container">
18
+ <ul>
19
+ ${this.alphabet.map(
20
+ letter =>
21
+ html`
22
+ <li
23
+ class=${letter === this.selectedUppercaseLetter
24
+ ? 'selected'
25
+ : nothing}
26
+ >
27
+ <a href="#" @click=${() => this.letterClicked(letter)}>
28
+ ${letter}
29
+ </a>
30
+ </li>
31
+ `
32
+ )}
33
+ </ul>
34
+ </div>
35
+ `;
36
+ }
37
+
38
+ private letterClicked(letter: string) {
39
+ if (letter === this.selectedUppercaseLetter) {
40
+ this.selectedLetter = undefined;
41
+ } else {
42
+ this.selectedLetter = letter;
43
+ }
44
+ this.dispatchEvent(
45
+ new CustomEvent('letterChanged', {
46
+ detail: { selectedLetter: this.selectedUppercaseLetter },
47
+ })
48
+ );
49
+ }
50
+
51
+ private readonly alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
52
+
53
+ static styles = css`
54
+ h1 {
55
+ font-size: 1.2rem;
56
+ }
57
+
58
+ #container {
59
+ background-color: #ddd;
60
+ color: #333;
61
+ }
62
+
63
+ ul {
64
+ list-style: none;
65
+ display: flex;
66
+ margin: 0;
67
+ padding: 0.5rem 1rem;
68
+ justify-content: space-between;
69
+ }
70
+
71
+ a {
72
+ color: #333;
73
+ text-decoration: none;
74
+ padding: 0.5rem 0.7rem;
75
+ display: block;
76
+ }
77
+
78
+ .selected {
79
+ background-color: darkgray;
80
+ }
81
+
82
+ .selected a {
83
+ color: white;
84
+ }
85
+ `;
86
+ }