coyotte508 HF staff commited on
Commit
edb4af2
1 Parent(s): fa79853

✨ Basic e-shop

Browse files
src/lib/components/Masonry.svelte ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { tick } from 'svelte';
3
+ export let items: any[] = []; // pass in data if it's dynamically updated
4
+
5
+ type GridItem = {
6
+ _el: HTMLElement;
7
+ gap: number;
8
+ items: HTMLElement[];
9
+ ncol: number;
10
+ mod: number;
11
+ };
12
+
13
+ let grids: GridItem[] = [];
14
+ let masonryElement: HTMLElement;
15
+
16
+ const refreshLayout = async () => {
17
+ masonryElement.querySelectorAll('img').forEach((img) => {
18
+ if (img.complete) {
19
+ img.classList.add('loaded');
20
+ }
21
+ });
22
+
23
+ grids.forEach(async (grid) => {
24
+ /* get the post relayout number of columns */
25
+ let ncol = getComputedStyle(grid._el).gridTemplateColumns.split(' ').length;
26
+
27
+ grid.items.forEach((c) => {
28
+ let new_h = c.getBoundingClientRect().height;
29
+
30
+ if (new_h !== Number(c.dataset.h)) {
31
+ c.dataset.h = String(new_h);
32
+ grid.mod++;
33
+ }
34
+ });
35
+
36
+ /* if the number of columns has changed */
37
+ if (grid.ncol !== ncol || grid.mod) {
38
+ /* update number of columns */
39
+ grid.ncol = ncol;
40
+ /* revert to initial positioning, no margin */
41
+ grid.items.forEach((c) => c.style.removeProperty('margin-top'));
42
+ /* if we have more than one column */
43
+ if (grid.ncol > 1) {
44
+ grid.items.slice(ncol).forEach((c, i) => {
45
+ let prev_fin =
46
+ grid.items[i].getBoundingClientRect().bottom /* bottom edge of item above */,
47
+ curr_ini = c.getBoundingClientRect().top; /* top edge of current item */
48
+
49
+ c.style.marginTop = `${prev_fin + grid.gap - curr_ini}px`;
50
+ });
51
+ }
52
+
53
+ grid.mod = 0;
54
+ }
55
+ });
56
+ };
57
+
58
+ const calcGrid = async (_masonryArr: HTMLElement[]) => {
59
+ await tick();
60
+ if (_masonryArr.length && getComputedStyle(_masonryArr[0]).gridTemplateRows !== 'masonry') {
61
+ grids = _masonryArr.map((grid) => {
62
+ return {
63
+ _el: grid,
64
+ gap: parseFloat(getComputedStyle(grid).rowGap),
65
+ items: [...(grid.childNodes as unknown as HTMLElement[])].filter(
66
+ (c) => c.nodeType === 1 && +getComputedStyle(c).gridColumnEnd !== -1
67
+ ),
68
+ ncol: 0,
69
+ mod: 0
70
+ };
71
+ });
72
+ refreshLayout(); /* initial load */
73
+ }
74
+ };
75
+
76
+ $: if (masonryElement) {
77
+ calcGrid([masonryElement]);
78
+ }
79
+
80
+ $: if (items) {
81
+ // update if items are changed
82
+ masonryElement = masonryElement; // refresh masonryElement
83
+
84
+ if (masonryElement) {
85
+ const images = masonryElement.querySelectorAll('img');
86
+
87
+ images.forEach((img) => {
88
+ img.removeEventListener('load', refreshLayout);
89
+ img.addEventListener('load', refreshLayout);
90
+ });
91
+ }
92
+ }
93
+ </script>
94
+
95
+ <svelte:window on:resize={refreshLayout} on:load={refreshLayout} />
96
+
97
+ <div bind:this={masonryElement} class="__grid--masonry">
98
+ <slot />
99
+ </div>
100
+
101
+ <!--
102
+ $w: var(--col-width); // minmax(Min(20em, 100%), 1fr);
103
+ $s: var(--grid-gap); // .5em;
104
+ -->
105
+ <style>
106
+ :global(.__grid--masonry) {
107
+ display: grid;
108
+ grid-template-columns: repeat(auto-fit, var(--col-width, minmax(Min(20em, 100%), 1fr)));
109
+ grid-template-rows: masonry;
110
+ justify-content: start;
111
+ grid-gap: var(--grid-gap, 0.5em);
112
+ }
113
+
114
+ :global(.__grid--masonry > *) {
115
+ align-self: start;
116
+ }
117
+ </style>
src/lib/server/db/index.ts CHANGED
@@ -19,3 +19,4 @@ const products = createProductCollection(db);
19
  const { pictures, picturesFs } = createPictureCollections(db);
20
 
21
  export { client, db, pages, users, pictures, picturesFs, products };
 
 
19
  const { pictures, picturesFs } = createPictureCollections(db);
20
 
21
  export { client, db, pages, users, pictures, picturesFs, products };
22
+ export const collections = { products, pictures };
src/routes/realisations/+page.svelte ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import Container from '$lib/components/Container.svelte';
3
+ import PictureComponent from '$lib/components/Picture.svelte';
4
+ import type { CreationsPage } from '$lib/types/Page';
5
+ import { marked } from 'marked';
6
+ import type { PageData } from './$types';
7
+
8
+ export let data: PageData;
9
+
10
+ const pageData = data.pageData as CreationsPage;
11
+ const pictures = data.pictures;
12
+
13
+ type PictureKey = keyof typeof pageData.pictures;
14
+
15
+ const picKeys = Object.keys(pageData.pictures).filter(
16
+ (key) => key.startsWith('realisation-') && pageData.pictures[key as PictureKey]
17
+ );
18
+ </script>
19
+
20
+ <Container>
21
+ <h1 class="text-4xl text-oxford mt-4">Nos réalisations</h1>
22
+
23
+ {#each picKeys as picKey, i}
24
+ <article
25
+ class="{i % 2
26
+ ? 'bg-oxford'
27
+ : 'bg-sunray'} text-white text-lg md:h-xl my-16 flex flex-wrap md:flex-no-wrap rounded-3xl overflow-hidden"
28
+ >
29
+ <div class="grow h-full w-full md:w-3/6 basis-auto md:basis-0" class:md:order-last={i % 2}>
30
+ <PictureComponent
31
+ picture={pictures.find((p) => p._id === pageData.pictures[picKey])}
32
+ sizes="(max-width: 1024px) 50vw, 512px"
33
+ class="w-full h-full object-cover"
34
+ />
35
+ </div>
36
+ <div class="grow basis-0 flex flex-col relative justify-center">
37
+ <div class="px-4 py-6">
38
+ {@html marked(pageData.text[picKey])}
39
+ <!-- svelte-ignore security-anchor-rel-noreferrer -->
40
+ <a
41
+ href="/photos/raw/{pictures.find((p) => p._id === pageData.pictures[picKey]).storage[0]
42
+ ._id}"
43
+ class="underline"
44
+ target="_blank">Photo entière</a
45
+ >
46
+ </div>
47
+ </div>
48
+ </article>
49
+ {/each}
50
+ </Container>
src/routes/vente/+page.server.ts ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { PageServerLoad } from './$types';
2
+ import '$lib/server/db';
3
+ import { collections } from '$lib/server/db';
4
+
5
+ export const load: PageServerLoad = async () => {
6
+ const products = await collections.products.find({ state: { $ne: 'draft' } }).toArray();
7
+ const pictures = await collections.pictures
8
+ .find({ productId: { $in: products.map((p) => p._id) } })
9
+ .sort({ createdAt: 1 })
10
+ .toArray();
11
+
12
+ const byId = Object.fromEntries(products.map((p) => [p._id, p]));
13
+
14
+ for (const picture of pictures) {
15
+ byId[picture.productId!].photos = [...(byId[picture.productId!].photos || []), picture];
16
+ }
17
+
18
+ return {
19
+ published: products.filter((p) => p.state === 'published'),
20
+ retired: products.filter((p) => p.state === 'retired')
21
+ };
22
+ };
src/routes/vente/+page.svelte ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import Container from '$lib/components/Container.svelte';
3
+ import Masonry from '$lib/components/Masonry.svelte';
4
+ import Picture from '$lib/components/Picture.svelte';
5
+ import type { EshopPage } from '$lib/types/Page';
6
+ import type { PageData } from './$types';
7
+
8
+ export let data: PageData;
9
+
10
+ const pictures = data.pictures;
11
+ const pageData = data.pageData as EshopPage;
12
+
13
+ const { published, retired } = data;
14
+ </script>
15
+
16
+ <div class="h-full-without-banner w-full relative">
17
+ <Picture
18
+ picture={pictures.find((p) => p._id === pageData.pictures.background)}
19
+ class="h-full w-full object-cover absolute top-0 bottom-0 left-0 right-0 bg-brunswick text-white"
20
+ style="z-index: -1"
21
+ />
22
+ <Container noPadding class="h-full flex flex-col items-stretch md:items-start">
23
+ <div class="grow basis-0" />
24
+ <div style="flex-grow: 2" class="basis-0 text-center md:px-8 xl:px-0">
25
+ <h1 class="text-7xl text-white text-center md:text-left">Découvrez <br />nos ventes</h1>
26
+ <a href="#produits" class="btn mt-10 inline-block">cliquez ici</a>
27
+ </div>
28
+ </Container>
29
+ </div>
30
+
31
+ <Container class="mb-4">
32
+ <h2 class="text-4xl text-oxford my-4" id="produits">Produits à la vente</h2>
33
+
34
+ <Masonry>
35
+ {#each published as product}
36
+ <a href="/vente/{product._id}" class="product">
37
+ <Picture picture={product.photos[0]} minStorage={1} class="picture mx-auto" />
38
+ <span class="name">{product.name}</span>
39
+ <span class="price text-right">{product.price} €</span>
40
+ </a>
41
+ {/each}
42
+ <!-- In case there is only one product. We don't want a product to take full row in desktop mode -->
43
+ <div />
44
+ </Masonry>
45
+ </Container>
46
+
47
+ <style>
48
+ .product {
49
+ display: grid;
50
+ gap: 0.5rem;
51
+ grid-template-columns: 1fr auto;
52
+ grid-template-rows: auto auto;
53
+ grid-template-areas:
54
+ 'picture picture'
55
+ 'name price';
56
+ }
57
+
58
+ :global(.product > .picture) {
59
+ grid-area: picture;
60
+ max-height: 24rem;
61
+ max-width: 100%;
62
+ }
63
+
64
+ .product > .name {
65
+ grid-area: name;
66
+
67
+ white-space: nowrap;
68
+ text-overflow: ellipsis;
69
+ overflow: hidden;
70
+ }
71
+
72
+ .product > .price {
73
+ grid-area: price;
74
+ }
75
+ </style>