@internetarchive/collection-browser 0.0.1-alpha.7 → 0.1.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.
Files changed (238) hide show
  1. package/README.md +8 -11
  2. package/demo/app-root.ts +30 -96
  3. package/dist/demo/app-root.d.ts +3 -5
  4. package/dist/demo/app-root.js +28 -87
  5. package/dist/demo/app-root.js.map +1 -1
  6. package/dist/index.d.ts +6 -0
  7. package/dist/index.js +6 -0
  8. package/dist/index.js.map +1 -1
  9. package/dist/src/assets/img/icons/chevron.d.ts +2 -0
  10. package/dist/src/assets/img/icons/chevron.js +4 -0
  11. package/dist/src/assets/img/icons/chevron.js.map +1 -0
  12. package/dist/src/assets/img/icons/eye-closed.d.ts +2 -0
  13. package/dist/src/assets/img/icons/eye-closed.js +5 -0
  14. package/dist/src/assets/img/icons/eye-closed.js.map +1 -0
  15. package/dist/src/assets/img/icons/eye.d.ts +2 -0
  16. package/dist/src/assets/img/icons/eye.js +5 -0
  17. package/dist/src/assets/img/icons/eye.js.map +1 -0
  18. package/dist/src/assets/img/icons/mediatype/account.d.ts +1 -2
  19. package/dist/src/assets/img/icons/mediatype/account.js +6 -4
  20. package/dist/src/assets/img/icons/mediatype/account.js.map +1 -1
  21. package/dist/src/assets/img/icons/mediatype/audio.js +7 -4
  22. package/dist/src/assets/img/icons/mediatype/audio.js.map +1 -1
  23. package/dist/src/assets/img/icons/mediatype/collection.js +7 -4
  24. package/dist/src/assets/img/icons/mediatype/collection.js.map +1 -1
  25. package/dist/src/assets/img/icons/mediatype/data.d.ts +1 -0
  26. package/dist/src/assets/img/icons/mediatype/data.js +15 -0
  27. package/dist/src/assets/img/icons/mediatype/data.js.map +1 -0
  28. package/dist/src/assets/img/icons/mediatype/etree.js +10 -5
  29. package/dist/src/assets/img/icons/mediatype/etree.js.map +1 -1
  30. package/dist/src/assets/img/icons/mediatype/film.js +2 -1
  31. package/dist/src/assets/img/icons/mediatype/film.js.map +1 -1
  32. package/dist/src/assets/img/icons/mediatype/images.js +9 -6
  33. package/dist/src/assets/img/icons/mediatype/images.js.map +1 -1
  34. package/dist/src/assets/img/icons/mediatype/radio.d.ts +1 -0
  35. package/dist/src/assets/img/icons/mediatype/radio.js +15 -0
  36. package/dist/src/assets/img/icons/mediatype/radio.js.map +1 -0
  37. package/dist/src/assets/img/icons/mediatype/software.js +9 -6
  38. package/dist/src/assets/img/icons/mediatype/software.js.map +1 -1
  39. package/dist/src/assets/img/icons/mediatype/texts.js +9 -6
  40. package/dist/src/assets/img/icons/mediatype/texts.js.map +1 -1
  41. package/dist/src/assets/img/icons/mediatype/tv.js +10 -5
  42. package/dist/src/assets/img/icons/mediatype/tv.js.map +1 -1
  43. package/dist/src/assets/img/icons/mediatype/video.js +10 -6
  44. package/dist/src/assets/img/icons/mediatype/video.js.map +1 -1
  45. package/dist/src/assets/img/icons/mediatype/web.js +9 -6
  46. package/dist/src/assets/img/icons/mediatype/web.js.map +1 -1
  47. package/dist/src/collection-browser.d.ts +57 -20
  48. package/dist/src/collection-browser.js +511 -128
  49. package/dist/src/collection-browser.js.map +1 -1
  50. package/dist/src/collection-facets.d.ts +27 -6
  51. package/dist/src/collection-facets.js +316 -100
  52. package/dist/src/collection-facets.js.map +1 -1
  53. package/dist/src/language-code-handler/language-code-handler.d.ts +37 -0
  54. package/dist/src/language-code-handler/language-code-handler.js +27 -0
  55. package/dist/src/language-code-handler/language-code-handler.js.map +1 -0
  56. package/dist/src/language-code-handler/language-code-mapping.d.ts +1 -0
  57. package/dist/src/language-code-handler/language-code-mapping.js +563 -0
  58. package/dist/src/language-code-handler/language-code-mapping.js.map +1 -0
  59. package/dist/src/mediatype/mediatype-config.d.ts +3 -0
  60. package/dist/src/mediatype/mediatype-config.js +86 -0
  61. package/dist/src/mediatype/mediatype-config.js.map +1 -0
  62. package/dist/src/models.d.ts +72 -13
  63. package/dist/src/models.js +57 -1
  64. package/dist/src/models.js.map +1 -1
  65. package/dist/src/restoration-state-handler.d.ts +38 -0
  66. package/dist/src/restoration-state-handler.js +204 -0
  67. package/dist/src/restoration-state-handler.js.map +1 -0
  68. package/dist/src/sort-filter-bar/alpha-bar.d.ts +1 -2
  69. package/dist/src/sort-filter-bar/alpha-bar.js +12 -8
  70. package/dist/src/sort-filter-bar/alpha-bar.js.map +1 -1
  71. package/dist/src/sort-filter-bar/img/compact.d.ts +1 -0
  72. package/dist/src/sort-filter-bar/img/compact.js +5 -0
  73. package/dist/src/sort-filter-bar/img/compact.js.map +1 -0
  74. package/dist/src/sort-filter-bar/img/list.d.ts +1 -0
  75. package/dist/src/sort-filter-bar/img/list.js +5 -0
  76. package/dist/src/sort-filter-bar/img/list.js.map +1 -0
  77. package/dist/src/sort-filter-bar/img/sort-triangle.d.ts +1 -0
  78. package/dist/src/sort-filter-bar/img/sort-triangle.js +5 -0
  79. package/dist/src/sort-filter-bar/img/sort-triangle.js.map +1 -0
  80. package/dist/src/sort-filter-bar/img/tile.d.ts +1 -0
  81. package/dist/src/sort-filter-bar/img/tile.js +5 -0
  82. package/dist/src/sort-filter-bar/img/tile.js.map +1 -0
  83. package/dist/src/sort-filter-bar/sort-filter-bar.d.ts +74 -13
  84. package/dist/src/sort-filter-bar/sort-filter-bar.js +547 -172
  85. package/dist/src/sort-filter-bar/sort-filter-bar.js.map +1 -1
  86. package/dist/src/tiles/{loading-tile.d.ts → collection-browser-loading-tile.d.ts} +1 -1
  87. package/dist/src/tiles/collection-browser-loading-tile.js +32 -0
  88. package/dist/src/tiles/collection-browser-loading-tile.js.map +1 -0
  89. package/dist/src/tiles/grid/account-tile.d.ts +1 -1
  90. package/dist/src/tiles/grid/account-tile.js +5 -5
  91. package/dist/src/tiles/grid/account-tile.js.map +1 -1
  92. package/dist/src/tiles/grid/collection-tile.js +1 -2
  93. package/dist/src/tiles/grid/collection-tile.js.map +1 -1
  94. package/dist/src/tiles/grid/icons/views.d.ts +1 -1
  95. package/dist/src/tiles/grid/icons/views.js +2 -2
  96. package/dist/src/tiles/grid/icons/views.js.map +1 -1
  97. package/dist/src/tiles/grid/item-tile.d.ts +2 -2
  98. package/dist/src/tiles/grid/item-tile.js +58 -150
  99. package/dist/src/tiles/grid/item-tile.js.map +1 -1
  100. package/dist/src/tiles/item-image.d.ts +19 -0
  101. package/dist/src/tiles/item-image.js +204 -0
  102. package/dist/src/tiles/item-image.js.map +1 -0
  103. package/dist/src/tiles/list/account-label.d.ts +1 -0
  104. package/dist/src/tiles/list/account-label.js +7 -0
  105. package/dist/src/tiles/list/account-label.js.map +1 -0
  106. package/dist/src/tiles/list/date-label.d.ts +1 -0
  107. package/dist/src/tiles/list/date-label.js +13 -0
  108. package/dist/src/tiles/list/date-label.js.map +1 -0
  109. package/dist/src/tiles/list/tile-list-compact-header.d.ts +12 -0
  110. package/dist/src/tiles/list/tile-list-compact-header.js +84 -0
  111. package/dist/src/tiles/list/tile-list-compact-header.js.map +1 -0
  112. package/dist/src/tiles/list/tile-list-compact.d.ts +12 -0
  113. package/dist/src/tiles/list/tile-list-compact.js +203 -6
  114. package/dist/src/tiles/list/tile-list-compact.js.map +1 -1
  115. package/dist/src/tiles/list/tile-list.d.ts +35 -10
  116. package/dist/src/tiles/list/tile-list.js +368 -104
  117. package/dist/src/tiles/list/tile-list.js.map +1 -1
  118. package/dist/src/{mediatype-icon.d.ts → tiles/mediatype-icon.d.ts} +2 -2
  119. package/dist/src/tiles/mediatype-icon.js +78 -0
  120. package/dist/src/tiles/mediatype-icon.js.map +1 -0
  121. package/dist/src/tiles/tile-dispatcher.d.ts +11 -4
  122. package/dist/src/tiles/tile-dispatcher.js +56 -19
  123. package/dist/src/tiles/tile-dispatcher.js.map +1 -1
  124. package/dist/src/utils/format-date.js +2 -2
  125. package/dist/src/utils/format-date.js.map +1 -1
  126. package/dist/test/collection-browser.test.d.ts +1 -0
  127. package/dist/test/collection-browser.test.js +16 -2
  128. package/dist/test/collection-browser.test.js.map +1 -1
  129. package/dist/test/{utils/format-string.test.d.ts → mediatype-config.test.d.ts} +0 -0
  130. package/dist/test/mediatype-config.test.js +17 -0
  131. package/dist/test/mediatype-config.test.js.map +1 -0
  132. package/dist/test/utils/format-date.test.js +1 -1
  133. package/dist/test/utils/format-date.test.js.map +1 -1
  134. package/index.ts +6 -0
  135. package/local.archive.org.cert +86 -0
  136. package/local.archive.org.key +27 -0
  137. package/package.json +9 -5
  138. package/src/assets/img/icons/chevron.ts +4 -0
  139. package/src/assets/img/icons/eye-closed.ts +5 -0
  140. package/src/assets/img/icons/eye.ts +5 -0
  141. package/src/assets/img/icons/mediatype/account.ts +6 -4
  142. package/src/assets/img/icons/mediatype/audio.ts +7 -4
  143. package/src/assets/img/icons/mediatype/collection.ts +7 -4
  144. package/src/assets/img/icons/mediatype/data.ts +15 -0
  145. package/src/assets/img/icons/mediatype/etree.ts +10 -5
  146. package/src/assets/img/icons/mediatype/film.ts +2 -1
  147. package/src/assets/img/icons/mediatype/images.ts +9 -6
  148. package/src/assets/img/icons/mediatype/radio.ts +15 -0
  149. package/src/assets/img/icons/mediatype/software.ts +9 -6
  150. package/src/assets/img/icons/mediatype/texts.ts +9 -6
  151. package/src/assets/img/icons/mediatype/tv.ts +10 -5
  152. package/src/assets/img/icons/mediatype/video.ts +10 -6
  153. package/src/assets/img/icons/mediatype/web.ts +9 -6
  154. package/src/collection-browser.ts +537 -123
  155. package/src/collection-facets.ts +352 -132
  156. package/src/language-code-handler/language-code-handler.ts +64 -0
  157. package/src/language-code-handler/language-code-mapping.ts +564 -0
  158. package/src/mediatype/mediatype-config.ts +86 -0
  159. package/src/models.ts +141 -13
  160. package/src/restoration-state-handler.ts +266 -0
  161. package/src/sort-filter-bar/alpha-bar.ts +12 -8
  162. package/src/sort-filter-bar/img/compact.ts +5 -0
  163. package/src/sort-filter-bar/img/list.ts +5 -0
  164. package/src/sort-filter-bar/img/sort-triangle.ts +5 -0
  165. package/src/sort-filter-bar/img/tile.ts +5 -0
  166. package/src/sort-filter-bar/sort-filter-bar.ts +604 -176
  167. package/src/tiles/collection-browser-loading-tile.ts +29 -0
  168. package/src/tiles/grid/account-tile.ts +1 -1
  169. package/src/tiles/grid/collection-tile.ts +1 -2
  170. package/src/tiles/grid/icons/views.ts +2 -2
  171. package/src/tiles/grid/item-tile.ts +57 -162
  172. package/src/tiles/item-image.ts +206 -0
  173. package/src/tiles/list/account-label.ts +6 -0
  174. package/src/tiles/list/date-label.ts +12 -0
  175. package/src/tiles/list/tile-list-compact-header.ts +77 -0
  176. package/src/tiles/list/tile-list-compact.ts +218 -0
  177. package/src/tiles/list/tile-list.ts +412 -107
  178. package/src/tiles/mediatype-icon.ts +75 -0
  179. package/src/tiles/tile-dispatcher.ts +66 -18
  180. package/src/utils/format-date.ts +2 -2
  181. package/test/collection-browser.test.ts +20 -1
  182. package/test/mediatype-config.test.ts +18 -0
  183. package/test/utils/format-date.test.ts +1 -1
  184. package/web-dev-server.config.mjs +3 -1
  185. package/dist/src/assets/img/icons/audio.d.ts +0 -1
  186. package/dist/src/assets/img/icons/audio.js +0 -9
  187. package/dist/src/assets/img/icons/audio.js.map +0 -1
  188. package/dist/src/assets/img/icons/collection.d.ts +0 -1
  189. package/dist/src/assets/img/icons/collection.js +0 -9
  190. package/dist/src/assets/img/icons/collection.js.map +0 -1
  191. package/dist/src/assets/img/icons/etree.d.ts +0 -1
  192. package/dist/src/assets/img/icons/etree.js +0 -9
  193. package/dist/src/assets/img/icons/etree.js.map +0 -1
  194. package/dist/src/assets/img/icons/images.d.ts +0 -1
  195. package/dist/src/assets/img/icons/images.js +0 -10
  196. package/dist/src/assets/img/icons/images.js.map +0 -1
  197. package/dist/src/assets/img/icons/mediatype/etree copy.d.ts +0 -1
  198. package/dist/src/assets/img/icons/mediatype/etree copy.js +0 -9
  199. package/dist/src/assets/img/icons/mediatype/etree copy.js.map +0 -1
  200. package/dist/src/assets/img/icons/mediatype/livemusic.d.ts +0 -1
  201. package/dist/src/assets/img/icons/mediatype/livemusic.js +0 -7
  202. package/dist/src/assets/img/icons/mediatype/livemusic.js.map +0 -1
  203. package/dist/src/assets/img/icons/mediatype/photos.d.ts +0 -1
  204. package/dist/src/assets/img/icons/mediatype/photos.js +0 -7
  205. package/dist/src/assets/img/icons/mediatype/photos.js.map +0 -1
  206. package/dist/src/assets/img/icons/software.d.ts +0 -1
  207. package/dist/src/assets/img/icons/software.js +0 -10
  208. package/dist/src/assets/img/icons/software.js.map +0 -1
  209. package/dist/src/assets/img/icons/texts.d.ts +0 -1
  210. package/dist/src/assets/img/icons/texts.js +0 -10
  211. package/dist/src/assets/img/icons/texts.js.map +0 -1
  212. package/dist/src/assets/img/icons/tv.d.ts +0 -1
  213. package/dist/src/assets/img/icons/tv.js +0 -9
  214. package/dist/src/assets/img/icons/tv.js.map +0 -1
  215. package/dist/src/assets/img/icons/video.d.ts +0 -1
  216. package/dist/src/assets/img/icons/video.js +0 -10
  217. package/dist/src/assets/img/icons/video.js.map +0 -1
  218. package/dist/src/assets/img/icons/web.d.ts +0 -1
  219. package/dist/src/assets/img/icons/web.js +0 -10
  220. package/dist/src/assets/img/icons/web.js.map +0 -1
  221. package/dist/src/mediatype-icon.js +0 -89
  222. package/dist/src/mediatype-icon.js.map +0 -1
  223. package/dist/src/search-handler.d.ts +0 -11
  224. package/dist/src/search-handler.js +0 -34
  225. package/dist/src/search-handler.js.map +0 -1
  226. package/dist/src/tiles/list/tile-list-detail.d.ts +0 -7
  227. package/dist/src/tiles/list/tile-list-detail.js +0 -28
  228. package/dist/src/tiles/list/tile-list-detail.js.map +0 -1
  229. package/dist/src/tiles/loading-tile.js +0 -73
  230. package/dist/src/tiles/loading-tile.js.map +0 -1
  231. package/dist/src/utils/format-string.d.ts +0 -2
  232. package/dist/src/utils/format-string.js +0 -7
  233. package/dist/src/utils/format-string.js.map +0 -1
  234. package/dist/test/utils/format-string.test.js +0 -17
  235. package/dist/test/utils/format-string.test.js.map +0 -1
  236. package/src/assets/img/icons/mediatype/foo.svg +0 -5
  237. package/src/mediatype-icon.ts +0 -83
  238. package/src/tiles/loading-tile.ts +0 -70
@@ -1,126 +1,85 @@
1
- import { SortParam } from '@internetarchive/search-service';
2
- import { LitElement, html, css, nothing, PropertyValues } from 'lit';
3
- import { customElement, property, state } from 'lit/decorators.js';
4
- import { CollectionDisplayMode } from '../models';
1
+ import {
2
+ LitElement,
3
+ html,
4
+ css,
5
+ nothing,
6
+ PropertyValues,
7
+ TemplateResult,
8
+ } from 'lit';
9
+ import { customElement, property, query, state } from 'lit/decorators.js';
10
+ import {
11
+ SharedResizeObserverInterface,
12
+ SharedResizeObserverResizeHandlerInterface,
13
+ } from '@internetarchive/shared-resize-observer';
14
+ import {
15
+ CollectionDisplayMode,
16
+ SortField,
17
+ SortFieldDisplayName,
18
+ } from '../models';
5
19
  import './alpha-bar';
6
20
 
21
+ import { sortIcon } from './img/sort-triangle';
22
+ import { tileIcon } from './img/tile';
23
+ import { listIcon } from './img/list';
24
+ import { compactIcon } from './img/compact';
25
+
26
+ type AlphaSelector = 'creator' | 'title';
27
+
7
28
  @customElement('sort-filter-bar')
8
- export class SortFilterBar extends LitElement {
9
- @property({ type: String }) displayMode: CollectionDisplayMode = 'grid';
29
+ export class SortFilterBar
30
+ extends LitElement
31
+ implements SharedResizeObserverResizeHandlerInterface
32
+ {
33
+ @property({ type: String }) displayMode?: CollectionDisplayMode;
34
+
35
+ @property({ type: String }) sortDirection: 'asc' | 'desc' | null = null;
36
+
37
+ @property({ type: String }) selectedSort: SortField = SortField.relevance;
38
+
39
+ @property({ type: String }) selectedTitleFilter: string | null = null;
10
40
 
11
- @property({ type: String }) sortDirection: 'asc' | 'desc' = 'desc';
41
+ @property({ type: String }) selectedCreatorFilter: string | null = null;
12
42
 
13
- @property({ type: String }) sortField = 'week';
43
+ @property({ type: Boolean }) showRelevance: boolean = true;
14
44
 
15
- @state() titleSelectorVisible: boolean = false;
45
+ @property({ type: Object }) resizeObserver?: SharedResizeObserverInterface;
16
46
 
17
- @state() creatorSelectorVisible: boolean = false;
47
+ @state() alphaSelectorVisible: AlphaSelector | null = null;
18
48
 
19
49
  @state() dateSortSelectorVisible = false;
20
50
 
51
+ @state() desktopSelectorBarWidth = 0;
52
+
53
+ @state() selectorBarContainerWidth = 0;
54
+
55
+ @state() hoveringOverDateSortOptions = false;
56
+
57
+ @query('#desktop-sort-selector')
58
+ private desktopSortSelector!: HTMLUListElement;
59
+
60
+ @query('#sort-selector-container')
61
+ private sortSelectorContainer!: HTMLDivElement;
62
+
21
63
  render() {
22
64
  return html`
23
65
  <div id="container">
24
66
  <div id="sort-bar">
25
- <div id="sort-selector">
26
- <ul>
27
- <li>
28
- <div id="sort-direction-container">
29
- <button
30
- class="sort-button"
31
- @click=${() => {
32
- this.sortDirection = 'asc';
33
- }}
34
- >
35
-
36
- </button>
37
- <button
38
- class="sort-button"
39
- @click=${() => {
40
- this.sortDirection = 'desc';
41
- }}
42
- >
43
-
44
- </button>
45
- </div>
46
- </li>
47
- <li>Sort By</li>
48
- <li>
49
- <a
50
- href="#"
51
- @click=${(e: Event) => {
52
- e.preventDefault();
53
- this.sortField = 'week';
54
- }}
55
- >
56
- Views
57
- </a>
58
- </li>
59
- <li>
60
- <a
61
- href="#"
62
- @click=${(e: Event) => {
63
- e.preventDefault();
64
- this.titleSelectorVisible = !this.titleSelectorVisible;
65
- this.sortField = 'titleSorter';
66
- }}
67
- >
68
- Title
69
- </a>
70
- </li>
71
- <li>
72
- <a
73
- href="#"
74
- @click=${(e: Event) => {
75
- e.preventDefault();
76
- this.dateSortSelectorVisible =
77
- !this.dateSortSelectorVisible;
78
- }}
79
- >
80
- Date Archived
81
- </a>
82
- </li>
83
- <li>
84
- <a
85
- href="#"
86
- @click=${(e: Event) => {
87
- e.preventDefault();
88
- this.creatorSelectorVisible = !this.creatorSelectorVisible;
89
- this.sortField = 'creatorSorter';
90
- }}
91
- >
92
- Creator
93
- </a>
94
- </li>
95
- </ul>
67
+ <div id="sort-direction-container">
68
+ ${this.sortDirectionSelectorTemplate}
96
69
  </div>
97
70
 
98
- <div id="display-style">
99
- <ul>
100
- ${this.displayMode !== 'grid'
101
- ? html`<li>
102
- <input type="checkbox" @click=${this.detailSelected} />
103
- Details
104
- </li>`
105
- : nothing}
106
-
107
- <li>
108
- <button id="grid-button" @click=${this.gridSelected}>
109
- Grid
110
- </button>
111
- </li>
112
- <li>
113
- <button id="list-button" @click=${this.listSelected}>
114
- List
115
- </button>
116
- </li>
117
- </ul>
71
+ <div id="sort-selector-container">
72
+ ${this.mobileSortSelectorTemplate}
73
+ ${this.desktopSortSelectorTemplate}
118
74
  </div>
75
+
76
+ <div id="display-style-selector">${this.displayOptionTemplate}</div>
119
77
  </div>
120
78
 
121
- ${this.dateSortSelectorVisible ? this.dateSortSelector : nothing}
122
- ${this.titleSelectorVisible ? this.titleSelectorBar : nothing}
123
- ${this.creatorSelectorVisible ? this.creatorSelectorBar : nothing}
79
+ ${this.dateSortSelectorVisible && !this.mobileSelectorVisible
80
+ ? this.dateSortSelector
81
+ : nothing}
82
+ ${this.alphaBarTemplate}
124
83
 
125
84
  <div id="bottom-shadow"></div>
126
85
  </div>
@@ -132,66 +91,400 @@ export class SortFilterBar extends LitElement {
132
91
  this.displayModeChanged();
133
92
  }
134
93
 
135
- if (changed.has('sortDirection') || changed.has('sortField')) {
136
- this.sortChanged();
94
+ if (changed.has('selectedSort') && this.sortDirection === null) {
95
+ this.sortDirection = 'desc';
96
+ }
97
+
98
+ if (changed.has('selectedTitleFilter') && this.selectedTitleFilter) {
99
+ this.alphaSelectorVisible = 'title';
100
+ }
101
+
102
+ if (changed.has('selectedCreatorFilter') && this.selectedCreatorFilter) {
103
+ this.alphaSelectorVisible = 'creator';
104
+ }
105
+
106
+ if (changed.has('dateSortSelectorVisible')) {
107
+ this.setupEscapeListeners();
108
+ }
109
+
110
+ if (changed.has('resizeObserver')) {
111
+ const oldObserver = changed.get(
112
+ 'resizeObserver'
113
+ ) as SharedResizeObserverInterface;
114
+ if (oldObserver) this.disconnectResizeObserver(oldObserver);
115
+ this.setupResizeObserver();
116
+ }
117
+ }
118
+
119
+ private setupEscapeListeners() {
120
+ if (this.dateSortSelectorVisible) {
121
+ document.addEventListener(
122
+ 'keydown',
123
+ this.boundDateSelectorEscapeListener
124
+ );
125
+ } else {
126
+ document.removeEventListener(
127
+ 'keydown',
128
+ this.boundDateSelectorEscapeListener
129
+ );
130
+ }
131
+ }
132
+
133
+ private boundDateSelectorEscapeListener = (e: KeyboardEvent) => {
134
+ if (e.key === 'Escape') {
135
+ this.dateSortSelectorVisible = false;
136
+ }
137
+ };
138
+
139
+ disconnectedCallback(): void {
140
+ if (this.resizeObserver) {
141
+ this.disconnectResizeObserver(this.resizeObserver);
142
+ }
143
+ }
144
+
145
+ private disconnectResizeObserver(
146
+ resizeObserver: SharedResizeObserverInterface
147
+ ) {
148
+ resizeObserver.removeObserver({
149
+ target: this.sortSelectorContainer,
150
+ handler: this,
151
+ });
152
+
153
+ resizeObserver.removeObserver({
154
+ target: this.desktopSortSelector,
155
+ handler: this,
156
+ });
157
+ }
158
+
159
+ private setupResizeObserver() {
160
+ if (!this.resizeObserver) return;
161
+ this.resizeObserver.addObserver({
162
+ target: this.sortSelectorContainer,
163
+ handler: this,
164
+ });
165
+
166
+ this.resizeObserver.addObserver({
167
+ target: this.desktopSortSelector,
168
+ handler: this,
169
+ });
170
+ }
171
+
172
+ private get mobileSelectorVisible() {
173
+ return this.selectorBarContainerWidth - 10 < this.desktopSelectorBarWidth;
174
+ }
175
+
176
+ private get alphaBarTemplate(): TemplateResult | typeof nothing {
177
+ if (!['title', 'creator'].includes(this.selectedSort)) return nothing;
178
+
179
+ if (this.alphaSelectorVisible === null) {
180
+ if (this.selectedSort === 'creator') return this.creatorSelectorBar;
181
+ if (this.selectedSort === 'title') return this.titleSelectorBar;
182
+ } else {
183
+ return this.alphaSelectorVisible === 'creator'
184
+ ? this.creatorSelectorBar
185
+ : this.titleSelectorBar;
186
+ }
187
+
188
+ return nothing;
189
+ }
190
+
191
+ handleResize(entry: ResizeObserverEntry): void {
192
+ if (entry.target === this.desktopSortSelector) {
193
+ this.desktopSelectorBarWidth = entry.contentRect.width;
194
+ } else if (entry.target === this.sortSelectorContainer) {
195
+ this.selectorBarContainerWidth = entry.contentRect.width;
196
+ }
197
+ }
198
+
199
+ private get sortDirectionSelectorTemplate() {
200
+ return html`
201
+ <div id="sort-direction-selector">
202
+ <button
203
+ id="sort-ascending-btn"
204
+ class="sort-button ${this.sortDirection === 'asc' ? 'selected' : ''}"
205
+ ?disabled=${this.selectedSort === 'relevance'}
206
+ @click=${() => {
207
+ this.setSortDirections('asc');
208
+ }}
209
+ >
210
+ ${sortIcon}
211
+ </button>
212
+ <button
213
+ id="sort-descending-btn"
214
+ class="sort-button ${this.sortDirection === 'desc' ? 'selected' : ''}"
215
+ ?disabled=${this.selectedSort === 'relevance'}
216
+ @click=${() => {
217
+ this.setSortDirections('desc');
218
+ }}
219
+ >
220
+ ${sortIcon}
221
+ </button>
222
+ </div>
223
+ `;
224
+ }
225
+
226
+ private get desktopSortSelectorTemplate() {
227
+ return html`
228
+ <ul
229
+ id="desktop-sort-selector"
230
+ class=${this.mobileSelectorVisible ? 'hidden' : 'visible'}
231
+ >
232
+ <li id="sort-by-text">Sort By</li>
233
+ <li>
234
+ ${this.showRelevance
235
+ ? this.getSortDisplayOption(SortField.relevance)
236
+ : nothing}
237
+ </li>
238
+ <li>${this.getSortDisplayOption(SortField.views)}</li>
239
+ <li>
240
+ ${this.getSortDisplayOption(SortField.title, {
241
+ clickEvent: () => {
242
+ this.alphaSelectorVisible = 'title';
243
+ this.selectedCreatorFilter = null;
244
+ this.dateSortSelectorVisible = false;
245
+ this.setSelectedSort(SortField.title);
246
+ this.emitCreatorLetterChangedEvent();
247
+ },
248
+ })}
249
+ </li>
250
+ <li>
251
+ ${this.getSortDisplayOption(SortField.date, {
252
+ clickEvent: () => {
253
+ if (!this.dateOptionSelected)
254
+ this.setSelectedSort(SortField.date);
255
+ this.dateSortSelectorVisible = !this.dateSortSelectorVisible;
256
+ this.alphaSelectorVisible = null;
257
+ this.selectedTitleFilter = null;
258
+ this.selectedCreatorFilter = null;
259
+ this.emitTitleLetterChangedEvent();
260
+ this.emitCreatorLetterChangedEvent();
261
+ },
262
+ displayName: html`${this.dateSortField}`,
263
+ isSelected: () => this.dateOptionSelected,
264
+ })}
265
+ </li>
266
+ <li>
267
+ ${this.getSortDisplayOption(SortField.creator, {
268
+ clickEvent: () => {
269
+ this.alphaSelectorVisible = 'creator';
270
+ this.selectedTitleFilter = null;
271
+ this.dateSortSelectorVisible = false;
272
+ this.setSelectedSort(SortField.creator);
273
+ this.emitTitleLetterChangedEvent();
274
+ },
275
+ })}
276
+ </li>
277
+ </ul>
278
+ `;
279
+ }
280
+
281
+ /**
282
+ * This generates each of the sort option links.
283
+ *
284
+ * It manages the display value and the selected state of the option.
285
+ *
286
+ * @param sortField
287
+ * @param options {
288
+ * additionalClickEvent?: () => void; If this is provided, it will also be called when the option is clicked.
289
+ * displayName?: TemplateResult; The name to display for the option. Defaults to the sortField display name.
290
+ * isSelected?: () => boolean; A function that returns true if the option is selected. Defaults to the selectedSort === sortField.
291
+ * }
292
+ * @returns
293
+ */
294
+ private getSortDisplayOption(
295
+ sortField: SortField,
296
+ options?: {
297
+ clickEvent?: (e: Event) => void;
298
+ isSelected?: () => boolean;
299
+ displayName?: TemplateResult;
137
300
  }
301
+ ): TemplateResult {
302
+ const isSelected =
303
+ options?.isSelected ?? (() => this.selectedSort === sortField);
304
+ const displayName = options?.displayName ?? SortFieldDisplayName[sortField];
305
+ return html`
306
+ <a
307
+ href="#"
308
+ @click=${(e: Event) => {
309
+ e.preventDefault();
310
+ if (options?.clickEvent) {
311
+ options.clickEvent(e);
312
+ } else {
313
+ this.alphaSelectorVisible = null;
314
+ this.dateSortSelectorVisible = false;
315
+ this.selectedTitleFilter = null;
316
+ this.selectedCreatorFilter = null;
317
+ this.setSelectedSort(sortField);
318
+ this.emitTitleLetterChangedEvent();
319
+ this.emitCreatorLetterChangedEvent();
320
+ }
321
+ }}
322
+ class=${isSelected() ? 'selected' : ''}
323
+ >
324
+ ${displayName}
325
+ </a>
326
+ `;
327
+ }
328
+
329
+ private get mobileSortSelectorTemplate() {
330
+ return html`
331
+ <select
332
+ id="mobile-sort-selector"
333
+ @change=${this.mobileSortChanged}
334
+ class=${this.mobileSelectorVisible ? 'visible' : 'hidden'}
335
+ >
336
+ ${Object.keys(SortField).map(
337
+ field => html`
338
+ <option value="${field}" ?selected=${this.selectedSort === field}>
339
+ ${SortFieldDisplayName[field as SortField]}
340
+ </option>
341
+ `
342
+ )}
343
+ </select>
344
+ `;
345
+ }
346
+
347
+ private mobileSortChanged(e: Event) {
348
+ const target = e.target as HTMLSelectElement;
349
+ this.setSelectedSort(target.value as SortField);
350
+ }
351
+
352
+ private get displayOptionTemplate() {
353
+ return html`
354
+ <ul>
355
+ <li>
356
+ <button
357
+ id="grid-button"
358
+ @click=${() => {
359
+ this.displayMode = 'grid';
360
+ }}
361
+ class=${this.displayMode === 'grid' ? 'active' : ''}
362
+ >
363
+ ${tileIcon}
364
+ </button>
365
+ </li>
366
+ <li>
367
+ <button
368
+ id="grid-button"
369
+ @click=${() => {
370
+ this.displayMode = 'list-detail';
371
+ }}
372
+ class=${this.displayMode === 'list-detail' ? 'active' : ''}
373
+ >
374
+ ${listIcon}
375
+ </button>
376
+ </li>
377
+ <li>
378
+ <button
379
+ id="list-button"
380
+ @click=${() => {
381
+ this.displayMode = 'list-compact';
382
+ }}
383
+ class=${this.displayMode === 'list-compact' ? 'active' : ''}
384
+ >
385
+ ${compactIcon}
386
+ </button>
387
+ </li>
388
+ </ul>
389
+ `;
138
390
  }
139
391
 
140
392
  private get dateSortSelector() {
141
393
  return html`
394
+ <div
395
+ id="date-sort-selector-backdrop"
396
+ @keyup=${() => {
397
+ this.dateSortSelectorVisible = false;
398
+ }}
399
+ @click=${() => {
400
+ this.dateSortSelectorVisible = false;
401
+ }}
402
+ ></div>
142
403
  <div id="date-sort-selector">
143
404
  <ul>
144
- <li>
145
- <button
146
- @click=${() => {
147
- this.sortField = 'publicdate';
148
- }}
149
- >
150
- Date Archived
151
- </button>
152
- </li>
153
- <li>
154
- <button
155
- @click=${() => {
156
- this.sortField = 'date';
157
- }}
158
- >
159
- Date Published
160
- </button>
161
- </li>
162
- <li>
163
- <button
164
- @click=${() => {
165
- this.sortField = 'reviewdate';
166
- }}
167
- >
168
- Date Reviewed
169
- </button>
170
- </li>
171
- <li>
172
- <button
173
- @click=${() => {
174
- this.sortField = 'addeddate';
175
- }}
176
- >
177
- Date Added
178
- </button>
179
- </li>
405
+ <li>${this.getDateSortButton(SortField.datearchived)}</li>
406
+ <li>${this.getDateSortButton(SortField.date)}</li>
407
+ <li>${this.getDateSortButton(SortField.datereviewed)}</li>
408
+ <li>${this.getDateSortButton(SortField.dateadded)}</li>
180
409
  </ul>
181
410
  </div>
182
411
  `;
183
412
  }
184
413
 
414
+ private getDateSortButton(sortField: SortField) {
415
+ return html`
416
+ <button
417
+ @click=${() => {
418
+ this.selectDateSort(sortField);
419
+ }}
420
+ class=${this.selectedSort === sortField ? 'selected' : ''}
421
+ >
422
+ ${SortFieldDisplayName[sortField]}
423
+ </button>
424
+ `;
425
+ }
426
+
427
+ private selectDateSort(sortField: SortField) {
428
+ this.dateSortSelectorVisible = false;
429
+ this.setSelectedSort(sortField);
430
+ }
431
+
432
+ private setSortDirections(sortDirection: 'asc' | 'desc') {
433
+ this.sortDirection = sortDirection;
434
+ this.emitSortChangedEvent();
435
+ }
436
+
437
+ private setSelectedSort(sort: SortField) {
438
+ this.selectedSort = sort;
439
+ this.emitSortChangedEvent();
440
+ }
441
+
442
+ /**
443
+ * There are four date sort options.
444
+ *
445
+ * This checks to see if the current sort is one of them.
446
+ *
447
+ * @readonly
448
+ * @private
449
+ * @type {boolean}
450
+ * @memberof SortFilterBar
451
+ */
452
+ private get dateOptionSelected(): boolean {
453
+ const dateSortFields: SortField[] = [
454
+ SortField.datearchived,
455
+ SortField.date,
456
+ SortField.datereviewed,
457
+ SortField.dateadded,
458
+ ];
459
+ return dateSortFields.includes(this.selectedSort);
460
+ }
461
+
462
+ /**
463
+ * The display name of the current date field
464
+ *
465
+ * @readonly
466
+ * @private
467
+ * @type {string}
468
+ * @memberof SortFilterBar
469
+ */
470
+ private get dateSortField(): string {
471
+ const defaultSort = SortFieldDisplayName[SortField.date];
472
+ const name = this.dateOptionSelected
473
+ ? SortFieldDisplayName[this.selectedSort] ?? defaultSort
474
+ : defaultSort;
475
+ return name;
476
+ }
477
+
185
478
  private get titleSelectorBar() {
186
479
  return html` <alpha-bar
187
- headline="Title Starts With"
480
+ .selectedLetter=${this.selectedTitleFilter}
188
481
  @letterChanged=${this.titleLetterChanged}
189
482
  ></alpha-bar>`;
190
483
  }
191
484
 
192
485
  private get creatorSelectorBar() {
193
486
  return html` <alpha-bar
194
- headline="Creator Starts With"
487
+ .selectedLetter=${this.selectedCreatorFilter}
195
488
  @letterChanged=${this.creatorLetterChanged}
196
489
  ></alpha-bar>`;
197
490
  }
@@ -199,33 +492,35 @@ export class SortFilterBar extends LitElement {
199
492
  private titleLetterChanged(
200
493
  e: CustomEvent<{ selectedLetter: string | undefined }>
201
494
  ) {
202
- const event = new CustomEvent('titleLetterChanged', {
203
- detail: { selectedLetter: e.detail.selectedLetter },
204
- });
205
- this.dispatchEvent(event);
495
+ this.selectedTitleFilter = e.detail.selectedLetter ?? null;
496
+ this.emitTitleLetterChangedEvent();
206
497
  }
207
498
 
208
499
  private creatorLetterChanged(
209
500
  e: CustomEvent<{ selectedLetter: string | undefined }>
210
501
  ) {
211
- const event = new CustomEvent('creatorLetterChanged', {
212
- detail: { selectedLetter: e.detail.selectedLetter },
213
- });
214
- this.dispatchEvent(event);
502
+ this.selectedCreatorFilter = e.detail.selectedLetter ?? null;
503
+ this.emitCreatorLetterChangedEvent();
215
504
  }
216
505
 
217
- private gridSelected() {
218
- this.displayMode = 'grid';
219
- }
220
-
221
- private listSelected() {
222
- this.displayMode = 'list-compact';
506
+ private emitTitleLetterChangedEvent() {
507
+ const event = new CustomEvent<{ selectedLetter: string | null }>(
508
+ 'titleLetterChanged',
509
+ {
510
+ detail: { selectedLetter: this.selectedTitleFilter },
511
+ }
512
+ );
513
+ this.dispatchEvent(event);
223
514
  }
224
515
 
225
- private detailSelected(e: Event) {
226
- this.displayMode = (e.target as HTMLInputElement).checked
227
- ? 'list-detail'
228
- : 'list-compact';
516
+ private emitCreatorLetterChangedEvent() {
517
+ const event = new CustomEvent<{ selectedLetter: string | null }>(
518
+ 'creatorLetterChanged',
519
+ {
520
+ detail: { selectedLetter: this.selectedCreatorFilter },
521
+ }
522
+ );
523
+ this.dispatchEvent(event);
229
524
  }
230
525
 
231
526
  private displayModeChanged() {
@@ -235,17 +530,22 @@ export class SortFilterBar extends LitElement {
235
530
  this.dispatchEvent(event);
236
531
  }
237
532
 
238
- private sortChanged() {
239
- const sort = new SortParam(this.sortField, this.sortDirection);
240
- const event = new CustomEvent('sortChanged', {
241
- detail: { sort },
533
+ private emitSortChangedEvent() {
534
+ const event = new CustomEvent<{
535
+ selectedSort: SortField;
536
+ sortDirection: 'asc' | 'desc' | null;
537
+ }>('sortChanged', {
538
+ detail: {
539
+ selectedSort: this.selectedSort,
540
+ sortDirection: this.sortDirection,
541
+ },
242
542
  });
243
543
  this.dispatchEvent(event);
244
544
  }
245
545
 
246
546
  static styles = css`
247
547
  #container {
248
- margin: 0 1rem 2.5rem 1rem;
548
+ position: relative;
249
549
  }
250
550
 
251
551
  #sort-bar {
@@ -253,7 +553,15 @@ export class SortFilterBar extends LitElement {
253
553
  justify-content: space-between;
254
554
  border: 1px solid rgb(232, 232, 232);
255
555
  align-items: center;
256
- padding: 0.1rem 0.5rem;
556
+ padding: 0.5rem 1.5rem;
557
+ }
558
+
559
+ #sort-direction-container {
560
+ flex: 0;
561
+ }
562
+
563
+ #sort-by-text {
564
+ text-transform: uppercase;
257
565
  }
258
566
 
259
567
  #bottom-shadow {
@@ -272,7 +580,7 @@ export class SortFilterBar extends LitElement {
272
580
  }
273
581
 
274
582
  li {
275
- padding: 0.5rem 0 0.5rem 0;
583
+ padding: 0;
276
584
  }
277
585
 
278
586
  .sort-button {
@@ -280,40 +588,160 @@ export class SortFilterBar extends LitElement {
280
588
  color: inherit;
281
589
  border: none;
282
590
  padding: 0;
283
- font: inherit;
284
591
  cursor: pointer;
285
592
  outline: inherit;
593
+ width: 12px;
594
+ height: 12px;
595
+ opacity: 0.5;
286
596
  }
287
597
 
288
- #sort-direction-container {
598
+ .sort-button.selected {
599
+ opacity: 1;
600
+ }
601
+
602
+ .sort-button:disabled {
603
+ opacity: 0.25;
604
+ cursor: default;
605
+ }
606
+
607
+ #date-sort-selector {
608
+ position: absolute;
609
+ left: 150px;
610
+ top: 45px;
611
+
612
+ z-index: 1;
613
+ padding: 1rem;
614
+ background-color: white;
615
+ border-radius: 2.5rem;
616
+ border: 1px solid #404142;
617
+ }
618
+
619
+ #date-sort-selector button {
620
+ background: none;
621
+ border-radius: 15px;
622
+ color: #404142;
623
+ border: none;
624
+ appearance: none;
625
+ cursor: pointer;
626
+ -webkit-appearance: none;
627
+ font-size: 1.4rem;
628
+ font-weight: 400;
629
+ padding: 0.5rem 1.2rem;
630
+ }
631
+
632
+ #date-sort-selector button.selected {
633
+ background-color: #404142;
634
+ color: white;
635
+ }
636
+
637
+ #show-details {
638
+ text-transform: uppercase;
639
+ cursor: pointer;
640
+ display: flex;
641
+ }
642
+
643
+ #show-details input {
644
+ margin-right: 0.5rem;
645
+ flex: 0 0 12px;
646
+ }
647
+
648
+ #sort-descending-btn {
649
+ transform: rotate(180deg);
650
+ }
651
+
652
+ #sort-direction-selector {
289
653
  display: flex;
290
654
  flex-direction: column;
655
+ gap: 3px;
656
+ margin-right: 1rem;
291
657
  }
292
658
 
293
- #sort-selector li {
659
+ #sort-selector-container {
660
+ flex: 1;
661
+ }
662
+
663
+ /*
664
+ we move the desktop sort selector offscreen instead of display: none
665
+ because we need to observe the width of it vs its container to determine
666
+ if it's wide enough to display the desktop version and if you displY: none,
667
+ the width becomes 0
668
+ */
669
+ #desktop-sort-selector.hidden {
670
+ position: absolute;
671
+ top: -9999px;
672
+ left: -9999px;
673
+ }
674
+
675
+ #mobile-sort-selector.hidden {
676
+ display: none;
677
+ }
678
+
679
+ #date-sort-selector-backdrop {
680
+ position: fixed;
681
+ top: 0;
682
+ left: 0;
683
+ width: 100%;
684
+ height: 100%;
685
+ z-index: 1;
686
+ background-color: rgba(255, 255, 255, 0.5);
687
+ }
688
+
689
+ #desktop-sort-selector {
690
+ display: inline-flex;
691
+ }
692
+
693
+ #desktop-sort-selector li {
294
694
  display: flex;
295
695
  align-items: center;
296
696
  }
297
697
 
298
- #sort-selector li a {
698
+ #desktop-sort-selector li a {
299
699
  text-decoration: none;
300
700
  text-transform: uppercase;
301
701
  font-size: 1.4rem;
302
702
  color: #333;
703
+ line-height: 2.5;
303
704
  }
304
705
 
305
- #sort-selector li::after {
706
+ #desktop-sort-selector li a.selected {
707
+ font-weight: bold;
708
+ }
709
+
710
+ #desktop-sort-selector li::after {
306
711
  content: '•';
307
712
  padding-left: 1rem;
308
713
  padding-right: 1rem;
309
714
  }
310
715
 
311
- #sort-selector li:first-child::after {
716
+ #desktop-sort-selector li:first-child::after {
312
717
  content: '';
313
718
  }
314
719
 
315
- #sort-selector li:last-child::after {
720
+ #desktop-sort-selector li:last-child::after {
316
721
  content: '';
317
722
  }
723
+
724
+ #display-style-selector {
725
+ flex: 0;
726
+ }
727
+
728
+ #display-style-selector button {
729
+ background: none;
730
+ color: inherit;
731
+ border: none;
732
+ appearance: none;
733
+ cursor: pointer;
734
+ -webkit-appearance: none;
735
+ opacity: 0.5;
736
+ }
737
+
738
+ #display-style-selector button.active {
739
+ opacity: 1;
740
+ }
741
+
742
+ #display-style-selector button svg {
743
+ width: 24px;
744
+ height: 24px;
745
+ }
318
746
  `;
319
747
  }