From 0202c4ac6e0b18ffd782c78dcc5c23738279ef0c Mon Sep 17 00:00:00 2001 From: Jon McClure Date: Mon, 22 Aug 2022 14:09:52 +0100 Subject: [PATCH] PhotoPack refactor --- .../PhotoPack/PhotoPack.stories.svelte | 101 +++++------ src/components/PhotoPack/PhotoPack.svelte | 169 +++++++----------- src/components/PhotoPack/docProps.ts | 44 +++++ .../PhotoPack/stories/docs/component.md | 27 ++- src/components/PhotoPack/stories/docs/more.md | 1 - .../PhotoPack/stories/docs/quickit.md | 65 +++++++ src/index.js | 4 + 7 files changed, 248 insertions(+), 163 deletions(-) create mode 100644 src/components/PhotoPack/docProps.ts delete mode 100644 src/components/PhotoPack/stories/docs/more.md create mode 100644 src/components/PhotoPack/stories/docs/quickit.md diff --git a/src/components/PhotoPack/PhotoPack.stories.svelte b/src/components/PhotoPack/PhotoPack.stories.svelte index 680c6f59..1db22aa2 100644 --- a/src/components/PhotoPack/PhotoPack.stories.svelte +++ b/src/components/PhotoPack/PhotoPack.stories.svelte @@ -4,9 +4,10 @@ // @ts-ignore import componentDocs from './stories/docs/component.md?raw'; // @ts-ignore - import moreDocs from './stories/docs/more.md?raw'; + import quickitDocs from './stories/docs/quickit.md?raw'; import PhotoPack from './PhotoPack.svelte'; + import { getPhotoPackPropsFromDoc } from './docProps'; import { withComponentDocs, withStoryDocs } from '$docs/utils/withParams.js'; @@ -28,7 +29,6 @@ const defaultImages = [ { - row: 1, src: 'https://via.placeholder.com/1024x768.jpg', altText: 'alt text', caption: @@ -36,21 +36,18 @@ maxHeight: 400, }, { - row: 2, src: 'https://via.placeholder.com/1640x1180.jpg', altText: 'alt text', caption: 'Surveillance footage shows a missile hitting a residential building in Kyiv, Ukraine, February 26, 2022, in this still image taken from a video obtained by REUTERS', }, { - row: 2, src: 'https://via.placeholder.com/1200x900.jpg', altText: 'alt text', caption: 'People walk past the remains of a missile at a bus terminal in Kyiv, Ukraine March 4, 2022. REUTERS/Valentyn Ogirenko', }, { - row: 2, src: 'https://via.placeholder.com/1024x768.jpg', altText: 'alt text', caption: @@ -58,43 +55,51 @@ }, ]; - const groupedImages = [ - { - row: 1, - src: 'https://via.placeholder.com/1024x768.jpg', - altText: 'alt text', - caption: - 'A residential building destroyed by shelling in the settlement of Borodyanka in the Kyiv region, Ukraine March 3, 2022. Picture taken with a drone. REUTERS/Maksim Levin', - maxHeight: 400, - }, - { - row: 2, - group: 1, - maxHeight: 300, - src: 'https://via.placeholder.com/1640x1180.jpg', - altText: 'alt text', - caption: - 'Surveillance footage shows a missile hitting a residential building in Kyiv, Ukraine, February 26, 2022, in this still image taken from a video obtained by REUTERS', - }, - { - row: 2, - group: 2, - maxHeight: 300, - src: 'https://via.placeholder.com/1200x900.jpg', - altText: 'alt text', - caption: - 'People walk past the remains of a missile at a bus terminal in Kyiv, Ukraine March 4, 2022. REUTERS/Valentyn Ogirenko', - }, - { - row: 2, - group: 2, - maxHeight: 300, - src: 'https://via.placeholder.com/1024x768.jpg', - altText: 'alt text', - caption: - 'People walk past the remains of a missile at a bus terminal. REUTERS/Valentyn Ogirenko', - }, + const defaultLayouts = [ + { breakpoint: 450, rows: [1, 2, 1] }, + { breakpoint: 750, rows: [1, 3] }, ]; + + const quickItBlock = { + Type: 'photo-pack', + ID: 'my-photo-pack', + Class: 'mb-2', + Width: 'wide', + CaptionWidth: 'normal', + Gap: '10', + Images: [ + { + Src: 'https://via.placeholder.com/1024x768.jpg', + AltText: 'alt text', + Caption: 'Lorem ipsum. Reuters/Photog', + MaxHeight: '600', + }, + { + Src: 'https://via.placeholder.com/1024x768.jpg', + AltText: 'alt text', + Caption: 'Lorem ipsum. Reuters/Photog', + }, + { + Src: 'https://via.placeholder.com/1024x768.jpg', + AltText: 'alt text', + Caption: 'Lorem ipsum. Reuters/Photog', + }, + { + Src: 'https://via.placeholder.com/1024x768.jpg', + AltText: 'alt text', + Caption: 'Lorem ipsum. Reuters/Photog', + }, + { + Src: 'https://via.placeholder.com/1024x768.jpg', + AltText: 'alt text', + Caption: 'Lorem ipsum. Reuters/Photog', + }, + ], + Layouts: [ + { Breakpoint: '750', Rows: '2,3' }, + { Breakpoint: '450', Rows: '1, 2, 2' }, + ], + }; @@ -107,18 +112,14 @@ name="Default" args="{{ width: 'wide', + captionWidth: 'normal', images: defaultImages, - breakRows: 750, + layouts: defaultLayouts, }}" /> diff --git a/src/components/PhotoPack/PhotoPack.svelte b/src/components/PhotoPack/PhotoPack.svelte index 81523bba..efb239d0 100644 --- a/src/components/PhotoPack/PhotoPack.svelte +++ b/src/components/PhotoPack/PhotoPack.svelte @@ -3,48 +3,23 @@ src: string; altText: string; caption?: string; - row?: number | string; - group?: number | string; - maxHeight?: number | string; + maxHeight?: number; } /** - * A set of images + * Array of image objects * @required */ export let images: Image[] = []; - // Coerce string values to numbers, where needed - $: imgs = images.map((img) => ({ - ...img, - row: !img.row - ? 1 - : typeof img.row === 'string' - ? parseInt(img.row) || 1 - : img.row, - group: !img.group - ? 1 - : typeof img.group === 'string' - ? parseInt(img.group) || 1 - : img.group, - maxHeight: !img.maxHeight - ? null - : img.maxHeight === 'string' - ? parseFloat(img.maxHeight) || null - : img.maxHeight, - })); - + interface Layout { + breakpoint: number; + rows: number[]; + } /** - * Container width below which to break rows. - * @type {number} + * Array of layout objects + * @required */ - export let breakRows: number = 900; - /** - * Container width below which to break groups, if groups specified in images. - * (Should be smaller than `breakRows`.) - * @type {number} - */ - export let breakGroups: number = breakRows; - + export let layouts: Layout[] = []; /** * Gap between images. * @type {number} @@ -80,24 +55,35 @@ import Block from '../Block/Block.svelte'; import PaddingReset from '../PaddingReset/index.svelte'; - import { groupBy } from 'lodash-es'; import { marked } from 'marked'; - const group = (groupArray, key = 'row') => { - const groupObj = groupBy(groupArray, (d) => d[key]); - const groupKeys = Object.keys(groupObj).sort(); - return groupKeys.map((k) => - key === 'row' ? group(groupObj[k], 'group') : groupObj[k] - ); - }; - - $: rows = group(imgs); - let containerWidth; - $: rowsBroken = (containerWidth || Infinity) <= breakRows; - $: groupsBroken = - (containerWidth || Infinity) <= - (breakGroups < breakRows ? breakGroups : breakRows); + + const groupRows = (images, layout) => { + // Default layout, one img per row + if (!layout) return images.map(img => [img]); + // Otherwise, chunk into rows according to layout scheme + let i = 0; + const rows = []; + for (const rowLength of layout.rows) { + const row = []; + for (const imgI of [...Array(rowLength).keys()]) { + row.push(images[imgI + i]); + } + rows.push(row); + i += rowLength; + } + return rows; + }; + // Sort so breakpoints always descend + $: layouts.sort((a, b) => a.breakpoint < b.breakpoint ? 1 : -1); + $: layout = layouts.find(l => ( + // Must have valid rows schema, i.e., adds to the total number of images + l.rows.reduce((a, b) => a + b, 0) === images.length && + // Breakpoint is higher than container width + ((containerWidth || 0) >= l.breakpoint) + )); + $: rows = groupRows(images, layout); @@ -107,28 +93,15 @@ class="photopack-row" style:gap="0 {gap}px" style:margin-bottom="{gap + 'px'}" - class:break="{rowsBroken}" > - {#each row as group, gi} -
- {#each group as img, i} -
- {img.altText} -
- {/each} -
+ {#each row as img, i} +
+ {img.altText} +
{/each} {/each} @@ -137,14 +110,12 @@
{#each rows as row, ri} - {#each row as group, gi} - {#each group as img, i} - {#if img.caption} -
- {@html marked(img.caption)} -
- {/if} - {/each} + {#each row as img, i} + {#if img.caption} +
+ {@html marked(img.caption)} +
+ {/if} {/each} {/each}
@@ -163,51 +134,33 @@ div.photopack-row { display: flex; justify-content: space-between; - &.break { - display: block; - div.photopack-group { - display: flex; - justify-content: space-between; - &.break { - display: block; - figure { - display: block; - max-height: unset; - margin-bottom: var(--gap); - } - } - } - } - div.photopack-group { - display: contents; - } - } - - figure { - flex: 1; - margin: 0; - padding: 0; - img { + figure { + flex: 1; margin: 0; - width: 100%; - height: 100%; - object-fit: cover; + padding: 0; + img { + margin: 0; + width: 100%; + height: 100%; + object-fit: cover; + } } } } div.captions-container { div.caption { - margin: 0 0 0.5rem; + margin: 0 0 0.6rem; &:last-of-type { margin-bottom: 0; } :global(p) { font-size: 0.85rem; - line-height: 1.15rem; + line-height: 1.10rem; font-family: var(--theme-font-family-note, $font-family-display); color: var(--theme-colour-text-secondary, $tr-medium-grey); margin: 0; + font-weight: 300; } } } diff --git a/src/components/PhotoPack/docProps.ts b/src/components/PhotoPack/docProps.ts new file mode 100644 index 00000000..df843d13 --- /dev/null +++ b/src/components/PhotoPack/docProps.ts @@ -0,0 +1,44 @@ +import urlJoin from 'proper-url-join'; + +interface BlockImage { + Src: string; + AltText: string; + Caption?: string; + MaxHeight?: string; +} + +interface BlockLayout { + Breakpoint: string; + Rows: string; +} + +interface Block { + Type: string; + ID?: string; + Class?: string; + Width: string; + CaptionWidth?: string; + Gap?: string; + Images: BlockImage[], + Layouts: BlockLayout[], +} + +export const getPhotoPackPropsFromDoc = (docBlock: Block, assetsPath: string = '') => { + return { + id: docBlock.ID, + cls: docBlock.Class, + width: docBlock.Width, + captionWidth: docBlock.CaptionWidth, + gap: docBlock.Gap && isNaN(docBlock.Gap as any) ? null : parseInt(docBlock.Gap), + images: docBlock.Images.map((img) => ({ + src: /^https?:\/\/|^\/\//i.test(img.Src) ? img.Src : urlJoin(assetsPath, img.Src), + altText: img.AltText, + caption: img.Caption, + maxHeight: img.MaxHeight && isNaN(img.MaxHeight as any) ? null : parseFloat(img.MaxHeight), + })), + layouts: docBlock.Layouts.map((layout) => ({ + breakpoint: isNaN(layout.Breakpoint as any) ? null : parseFloat(layout.Breakpoint), + rows: layout.Rows.split(',').map(r => parseInt(r.trim())), + })), + }; +}; diff --git a/src/components/PhotoPack/stories/docs/component.md b/src/components/PhotoPack/stories/docs/component.md index e3c8f315..2a34e327 100644 --- a/src/components/PhotoPack/stories/docs/component.md +++ b/src/components/PhotoPack/stories/docs/component.md @@ -1,11 +1,30 @@ -> **Coming soon!** - ---- +The `PhotoPack` component makes simple photo grids with custom layouts at whatever breakpoint you need. ```svelte - + ``` diff --git a/src/components/PhotoPack/stories/docs/more.md b/src/components/PhotoPack/stories/docs/more.md deleted file mode 100644 index 9658213d..00000000 --- a/src/components/PhotoPack/stories/docs/more.md +++ /dev/null @@ -1 +0,0 @@ -You can set sub groups and maxHeights... diff --git a/src/components/PhotoPack/stories/docs/quickit.md b/src/components/PhotoPack/stories/docs/quickit.md new file mode 100644 index 00000000..e2626880 --- /dev/null +++ b/src/components/PhotoPack/stories/docs/quickit.md @@ -0,0 +1,65 @@ +Setup your Google Doc with the images, layouts and other props you want: + +```yaml +# Google doc block +Type: photo-pack +ID: my-photo-pack +Class: mb-2 # adjust margin class as needed +Width: wide +CaptionWidth: normal +Gap: 10 +[.Images] + Src: images/my-img-1.jpg + AltText: Alt text + Caption: Lorem ipsum. REUTERS/Photog + MaxHeight: 600 # Optional max-height + + Src: images/my-img-2.jpg + AltText: Alt text + Caption: Lorem ipsum. REUTERS/Photog + + Src: images/my-img-3.jpg + AltText: Alt text + Caption: Lorem ipsum. REUTERS/Photog + + Src: images/my-img-4.jpg + AltText: Alt text + Caption: Lorem ipsum. REUTERS/Photog + + Src: images/my-img-5.jpg + AltText: Alt text + Caption: Lorem ipsum. REUTERS/Photog +[] +[.Layouts] + Breakpoint: 750 + Rows: 2,3 # Must add to total number of images! + + Breakpoint: 450 + Rows: 1,2,2 +[] +``` + +In your project, you can use the `getPhotoPackPropsFromDoc` utilty to easily convert the GoogleDoc format above into the props the `PhotoPack` component expects. + +```svelte + + + +{#each content.blocks as block} + {#if block.Type === 'text'} + + + + {:else if block.Type === 'photo-pack'} + + + + {/if} +{/each} +``` diff --git a/src/index.js b/src/index.js index 749daa06..89b14ddb 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,4 @@ +// Components export { default as Article } from './components/Article/Article.svelte'; export { default as BeforeAfter } from './components/BeforeAfter/BeforeAfter.svelte'; export { default as Block } from './components/Block/Block.svelte'; @@ -25,3 +26,6 @@ export { } from './components/Theme/Theme.svelte'; export { default as Video } from './components/Video/Video.svelte'; export { default as Visible } from './components/Visible/Visible.svelte'; + +// Utilities +export { getPhotoPackPropsFromDoc } from './components/PhotoPack/docProps';