/ src / context / today-card-layout.ts
today-card-layout.ts
 1  import { getContext, setContext } from 'svelte';
 2  
 3  import type { TodayPage } from '@jet-app/app-store/api/models';
 4  import {
 5      type TodayCardShelf,
 6      isTodayCardShelf,
 7  } from '~/components/jet/shelf/TodayCardShelf.svelte';
 8  
 9  /**
10   * Describes the configuration of the card layout within a {@linkcode TodayCardShelf}
11   */
12  interface LayoutConfiguration {
13      wrap: {
14          shouldStretchFirstCard: boolean;
15      };
16      nowrap: {
17          shouldStretchFirstCard: boolean;
18      };
19  }
20  
21  const LAYOUT_CONFIGURATION_FALLBACK: LayoutConfiguration = Object.freeze({
22      wrap: {
23          shouldStretchFirstCard: true,
24      },
25      nowrap: {
26          shouldStretchFirstCard: true,
27      },
28  });
29  
30  type TodayCardLayoutStore = WeakMap<TodayCardShelf, LayoutConfiguration>;
31  type TodayCardLayoutStoreContext = TodayCardLayoutStore | undefined;
32  
33  const TODAY_CARD_LAYOUT_CONTEXT_ID = 'today-card-layout-context';
34  
35  /**
36   * Store the {@linkcode LayoutConfiguration} for each {@linkcode TodayCardShelf} in a
37   * {@linkcode TodayPage} in "context", so it can be retrieved at the shelf-component level
38   *
39   * This is necessary because the layout of the cards within each shelf of a {@linkcode TodayPage}
40   * is only knowable given information about the shelves that were rendered before it
41   *
42   * The information about the shelf layout is persisted through the "context" API so that the
43   * rendering of a {@linkcode TodayPage} can defer to the "default" page component, which requires
44   * that we pass no additional arguments into each shelf component
45   *
46   * {@linkcode getTodayCardLayoutConfiguration} can be used to look up the {@linkcode LayoutConfiguration}
47   * stored for a given {@linkcode TodayCardShelf}
48   */
49  export function setTodayCardLayoutContext(page: Pick<TodayPage, 'shelves'>) {
50      const store: TodayCardLayoutStore = new WeakMap();
51  
52      let shouldStretchFirstCardMultiline = false;
53      let shouldStretchFirstCardInline = false;
54  
55      for (const shelf of page.shelves) {
56          // Skip any non-`TodayCard` shelves
57          if (!isTodayCardShelf(shelf)) {
58              continue;
59          }
60  
61          store.set(shelf, {
62              wrap: {
63                  shouldStretchFirstCard: shouldStretchFirstCardMultiline,
64              },
65              nowrap: {
66                  shouldStretchFirstCard: shouldStretchFirstCardInline,
67              },
68          });
69  
70          // In the multi-line card configuration, shelves with two or three cards in them will
71          // require that the next shelf swaps to stretching the cards at the opposite end
72          if (shelf.items.length === 2 || shelf.items.length === 3) {
73              shouldStretchFirstCardMultiline = !shouldStretchFirstCardMultiline;
74          }
75  
76          // In the "inline" card configuration, each shelf should always alternate which end the
77          // card is stretched on
78          shouldStretchFirstCardInline = !shouldStretchFirstCardInline;
79      }
80  
81      setContext<TodayCardLayoutStoreContext>(
82          TODAY_CARD_LAYOUT_CONTEXT_ID,
83          store,
84      );
85  }
86  
87  /**
88   * Retrieve the {@linkcode LayoutConfiguration} for a given {@linkcode TodayCardShelf}
89   */
90  export function getTodayCardLayoutConfiguration(
91      shelf: TodayCardShelf,
92  ): LayoutConfiguration {
93      const todayCardLayout = getContext<TodayCardLayoutStoreContext>(
94          TODAY_CARD_LAYOUT_CONTEXT_ID,
95      );
96  
97      return todayCardLayout?.get(shelf) ?? LAYOUT_CONFIGURATION_FALLBACK;
98  }