1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
  | 
import {empty, stitchArrays, unique} from '#sugar';
export default {
  relations: (relation) => ({
    actionLinks:
      relation('generateGridActionLinks'),
    expando:
      relation('generateGridExpando'),
  }),
  slots: {
    attributes: {type: 'attributes', mutable: false},
    images: {validate: v => v.strictArrayOf(v.isHTML)},
    links: {validate: v => v.strictArrayOf(v.isHTML)},
    names: {validate: v => v.strictArrayOf(v.isHTML)},
    info: {validate: v => v.strictArrayOf(v.isHTML)},
    tab: {validate: v => v.strictArrayOf(v.isHTML)},
    notFromThisGroup: {validate: v => v.strictArrayOf(v.isBoolean)},
    // Differentiating from sparseArrayOf here - this list of classes should
    // have the same length as the items above, i.e. nulls aren't going to be
    // filtered out of it, but it is okay to *include* null (standing in for
    // no classes for this grid item).
    classes: {
      validate: v =>
        v.strictArrayOf(
          v.optional(
            v.anyOf(
              v.isArray,
              v.isString))),
    },
    itemAttributes: {
      validate: v =>
        v.strictArrayOf(
          v.optional(v.isAttributes)),
    },
    lazy: {validate: v => v.anyOf(v.isWholeNumber, v.isBoolean)},
    actionLinks: {validate: v => v.sparseArrayOf(v.isHTML)},
    revealAllWarnings: {
      validate: v => v.looseArrayOf(v.isString),
    },
    bottomCaption: {
      type: 'html',
      mutable: false,
    },
    cutIndex: {validate: v => v.isWholeNumber},
  },
  generate: (relations, slots, {html, language}) =>
    html.tag('div', {class: 'grid-listing'},
      slots.attributes,
      {[html.onlyIfContent]: true},
      [
        !empty((slots.revealAllWarnings ?? []).filter(Boolean)) &&
          language.encapsulate('misc.coverGrid.revealAll', capsule =>
            html.tag('div', {class: 'reveal-all-container'},
              ((slots.tab ?? [])
                .slice(0, 4)
                .some(tab => tab && !html.isBlank(tab))) &&
                {class: 'has-nearby-tab'},
              html.tag('p', {class: 'reveal-all'}, [
                html.tag('a', {href: '#'}, [
                  html.tag('span', {class: 'reveal-label'},
                    language.$(capsule, 'reveal')),
                  html.tag('span', {class: 'conceal-label'},
                    {style: 'display: none'},
                    language.$(capsule, 'conceal')),
                ]),
                html.tag('br'),
                html.tag('span', {class: 'warnings'},
                  language.$(capsule, 'warnings', {
                    warnings:
                      language.formatUnitList(
                        unique(slots.revealAllWarnings.filter(Boolean))
                          .sort()
                          .map(warning => html.tag('b', warning))),
                  })),
              ]))),
        stitchArrays({
          classes: slots.classes,
          attributes: slots.itemAttributes,
          image: slots.images,
          link: slots.links,
          name: slots.names,
          info: slots.info,
          tab: slots.tab,
          notFromThisGroup:
            slots.notFromThisGroup ??
            Array.from(slots.links).fill(null)
        }).map(({
            classes,
            attributes,
            image,
            link,
            name,
            info,
            tab,
            notFromThisGroup,
          }, index) =>
            link.slots({
              attributes: [
                link.getSlotValue('attributes'),
                {class: ['grid-item', 'box']},
                tab &&
                !html.isBlank(tab) &&
                  {class: 'has-tab'},
                attributes,
                (classes
                  ? {class: classes}
                  : null),
                slots.cutIndex >= 1 &&
                index >= slots.cutIndex &&
                  {class: 'hidden-by-expandable-cut'},
              ],
              colorContext: 'image-box',
              content: [
                html.tag('span',
                  {[html.onlyIfContent]: true},
                  tab),
                image.slots({
                  thumb: 'medium',
                  square: true,
                  lazy:
                    (typeof slots.lazy === 'number'
                      ? index >= slots.lazy
                   : typeof slots.lazy === 'boolean'
                      ? slots.lazy
                      : false),
                }),
                html.tag('span',
                  {[html.onlyIfContent]: true},
                  (notFromThisGroup
                    ? language.encapsulate('misc.coverGrid.details.notFromThisGroup', capsule =>
                        language.$(capsule, {
                          name,
                          marker:
                            html.tag('span', {class: 'grid-name-marker'},
                              language.$(capsule, 'marker')),
                        }))
                    : language.sanitize(name))),
                html.tag('span',
                  {[html.onlyIfContent]: true},
                  language.$('misc.coverGrid.details.accent', {
                    [language.onlyIfOptions]: ['details'],
                    details: info,
                  })),
              ],
            })),
        relations.actionLinks
          .slot('actionLinks', slots.actionLinks),
        (slots.cutIndex >= 1 &&
         slots.cutIndex < slots.links.length
          ? relations.expando.slots({
              caption: slots.bottomCaption,
            })
       : !html.isBlank(relations.bottomCaption)
          ? html.tag('p', {class: 'grid-caption'},
              slots.caption)
          : html.blank()),
      ]),
};
  |