Merge branch 'dev' into 23-implement-unit-testing-helper-functions
This commit is contained in:
commit
9bb2689989
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* @param {rating[]} ratings An array of ratings
|
||||
* @returns {number} Average rating
|
||||
*/
|
||||
export const getAvgRating = (ratings) => {
|
||||
if (!ratings) return 0;
|
||||
if (ratings.length === 1) return ratings[0].rating;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export * from './toProduct';
|
||||
export * from './parse';
|
||||
export * from './normalize';
|
||||
export * from './getAvgRating';
|
||||
export * from './getAvgRating';
|
||||
export * from './param';
|
|
@ -1,2 +0,0 @@
|
|||
export const normalize = (str) => str.toLowerCase().trim()
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import { normalize, parseSlug, parseName } from './';
|
||||
|
||||
/**
|
||||
* Gets parameter from URL of field. Defaults to 'product'
|
||||
* @param {string} field='product'
|
||||
* @returns {string} parameter of field
|
||||
*/
|
||||
export const getProdParam = (field = 'product') =>
|
||||
new URLSearchParams(window.location.search).get(field) || ''
|
||||
|
||||
/**
|
||||
* Finds a product in products store with matching name as parameter.
|
||||
* @param {*} param parameter
|
||||
* @param {*} $products products store
|
||||
* @returns
|
||||
*/
|
||||
export const findProdFromParam = (param, $products) =>
|
||||
$products.find(({ name }) => normalize(name) === parseSlug(param)) || {}
|
||||
|
||||
/**
|
||||
* Resets params and navigates back to index
|
||||
*/
|
||||
export const resetParams = () => history.pushState({}, '', new URL(window.location.origin));
|
||||
|
||||
/**
|
||||
* Updates a url with search parameters with name of product parameter.
|
||||
* @param {URL} url a URL object
|
||||
* @param {object} product
|
||||
* @returns {URL} a URL object with search parameters appended.
|
||||
*/
|
||||
export const setUrlParam = (url, product) => {
|
||||
url.searchParams.set('product', parseName(product.name));
|
||||
return url;
|
||||
};
|
||||
|
||||
|
|
@ -1,9 +1,22 @@
|
|||
import {normalize }from './normalize';
|
||||
|
||||
/**
|
||||
* @param {string} str Any string
|
||||
* @returns {string} Lowercased string without any extra spaces
|
||||
*/
|
||||
export const normalize = (str) => str.toLowerCase().trim()
|
||||
|
||||
/**
|
||||
* @param {string} slug Slug of a product name
|
||||
* @returns Normalized slug with all dashes replaced with spaces
|
||||
*/
|
||||
export function parseSlug(slug) {
|
||||
return normalize(slug).replaceAll("-", " ")
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name Product name
|
||||
* @returns Normalized name with all spaces replaced with '-'
|
||||
*/
|
||||
export function parseName(name) {
|
||||
return normalize(name).replaceAll(" ","-")
|
||||
}
|
|
@ -1,8 +1,24 @@
|
|||
import {normalize, parseName} from './index';
|
||||
import { parseSlug, normalize, setUrlParam, resetParams } from './';
|
||||
|
||||
export const toProduct = (product, currentProduct) => {
|
||||
const url = new URL(window.location);
|
||||
url.searchParams.set('product', normalize(parseName(product.name)));
|
||||
window.history.pushState({}, '', url);
|
||||
currentProduct.set(product)
|
||||
}
|
||||
/**
|
||||
* Updates Window URL and checks that it is equal to product name
|
||||
* @param {URL} url a URL object
|
||||
* @param {product} product product to check URL against
|
||||
* @returns {Boolean} True if URL parameter is equal to product name
|
||||
*/
|
||||
const didWinUrlUpdate = (url, product) => {
|
||||
window.history.pushState(window.history.state, '', url);
|
||||
const params = new URLSearchParams(window.location.search).get('product');
|
||||
return parseSlug(params) === normalize(product.name);
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes a `product` and `currentProduct` store
|
||||
* Sets URL params to parsed product name and updates Browser URL
|
||||
* Sets `currentProduct` store to `product`.
|
||||
* @param {object} product The product to switch feature to.
|
||||
* @param {store} currentProduct currentProduct store
|
||||
*/
|
||||
export const toProduct = (product, currentProduct) =>
|
||||
didWinUrlUpdate(setUrlParam(new URL(window.location), product), product)?
|
||||
currentProduct.set(product): resetParams()
|
|
@ -2,6 +2,7 @@
|
|||
import { urlFor } from '$lib/sanityClient';
|
||||
import { currentProduct } from '$lib/stores';
|
||||
import Rating from './Rating/Rating.svelte';
|
||||
import PrevNext from './PrevNext/PrevNext.svelte';
|
||||
import Tag from './Tag/Tag.svelte';
|
||||
|
||||
const { container, imageView, name, description, productInfo, date, tags, img, ratings } = {
|
||||
|
@ -9,7 +10,7 @@
|
|||
imageView: 'flex m-16 gap-12 w-100',
|
||||
productInfo: 'flex flex-col gap-2',
|
||||
name: 'font-bold text-2xl',
|
||||
description: '',
|
||||
description: 'leading-5',
|
||||
date: 'text-xs w-2/3 mt-auto mb-4',
|
||||
tags: 'flex gap-1',
|
||||
img: 'p-8 border w-72 h-72 min-w-36 min-h-36 border-black border-2 p-3',
|
||||
|
@ -44,6 +45,7 @@
|
|||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<PrevNext />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
<script>
|
||||
import { currentProduct, productsView } from '$lib/stores';
|
||||
import { toProduct } from '$helpers';
|
||||
|
||||
$: foundIndex = $productsView.findIndex((prod) => prod._id === $currentProduct._id);
|
||||
$: PREV = $productsView[foundIndex - 1];
|
||||
$: NEXT = $productsView[foundIndex + 1];
|
||||
|
||||
const navigate = (e) => {
|
||||
if (e.target.name === 'prev' && PREV) {
|
||||
toProduct(PREV, currentProduct);
|
||||
}
|
||||
if (e.target.name === 'next' && NEXT) {
|
||||
toProduct(NEXT, currentProduct);
|
||||
}
|
||||
};
|
||||
|
||||
const { container, btn } = {
|
||||
container: 'flex justify-between m-16 mt-2 mb-6 h-12 ',
|
||||
btn: 'flex items-center gap-4'
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class={container}>
|
||||
{#if PREV}
|
||||
<button on:click={navigate} class={btn} name={'prev'}
|
||||
><svg focusable="false" width={50} height={50} viewBox="0 0 24 24">
|
||||
<path d="m14 7-5 5 5 5V7z" />
|
||||
</svg><span class="text-left">{PREV.name}</span></button
|
||||
>
|
||||
{:else}
|
||||
<div />
|
||||
{/if}
|
||||
|
||||
{#if NEXT}
|
||||
<button on:click={navigate} class={btn} name={'next'}
|
||||
><span class="text-right">{NEXT.name}</span><svg
|
||||
focusable="false"
|
||||
width={50}
|
||||
height={50}
|
||||
viewBox="0 0 24 24"><path d="m10 17 5-5-5-5v10z" /></svg
|
||||
></button
|
||||
>
|
||||
{:else}
|
||||
<div />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
svg,
|
||||
path,
|
||||
span {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
|
@ -39,7 +39,7 @@
|
|||
};
|
||||
|
||||
const { container, filterBar, filterTitle, filterSort } = {
|
||||
container: 'flex flex-col text-sm h-auto mb-4 mr-6',
|
||||
container: 'flex flex-col text-sm h-auto mb-4 mr-6 mt-4',
|
||||
filterBar: 'pl-8 p-2 pr-2 flex justify-between',
|
||||
filterTitle: 'font-bold',
|
||||
filterSort: 'p-1 pl-9 mr-12 hover:bg-blue-300 w-full focus:outline-none'
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
export let reset;
|
||||
const { title } = {
|
||||
title: 'hover:bg-blue-300 text-lg font-bold m-12 ml-0 pl-12'
|
||||
title: 'hover:bg-blue-300 text-lg font-bold p-12 pl-12 pt-11 snap-start sticky top-0 bg-white h-28 shadow-sm shadow-white'
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
const { container, productStyle } = {
|
||||
container: 'pt-4',
|
||||
productList: 'flex flex-col items-start mt-10 text-sm',
|
||||
productStyle: 'w-full text-left'
|
||||
productStyle: 'w-full text-left snap-start snap-always'
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
|||
{#each productsView as product}
|
||||
<button class={productStyle} on:click={() => toProduct(product, currentProduct)}>
|
||||
<p
|
||||
class={`pl-12 hover:bg-gray-200 ${
|
||||
class={`pl-10 hover:bg-gray-200 ${
|
||||
$currentProduct && $currentProduct.name === product.name ? 'bg-blue-300' : ''
|
||||
} text-sm`}
|
||||
>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
};
|
||||
|
||||
const { container, input } = {
|
||||
container: 'h-16 ml-auto mr-auto',
|
||||
container: 'h-12 ml-auto mr-auto flex justify-center items-center',
|
||||
input: 'outline outline-1 w-36 focus:outline-blue-600 p-1 text-sm'
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -16,22 +16,25 @@
|
|||
filters = { selectedCat: 0, selectedRating: 0 };
|
||||
};
|
||||
|
||||
const { main, sidebar } = {
|
||||
const { main, container, sidebar } = {
|
||||
main: 'flex w-screen h-screen sfmono',
|
||||
sidebar: 'flex flex-col justify-start h-screen overflow-auto w-52 shrink-0'
|
||||
container: 'flex flex-col h-screen justify-start shrink-0 overflow-auto w-56',
|
||||
sidebar: ' flex flex-col shrink-0'
|
||||
};
|
||||
</script>
|
||||
|
||||
<main class={main}>
|
||||
<div class={sidebar}>
|
||||
<div class={container}>
|
||||
<Header {reset} />
|
||||
{#if !Object.keys($currentProduct).length}
|
||||
<div class={sidebar}>
|
||||
<Search />
|
||||
{#if !Object.keys($currentProduct).length}
|
||||
<Filters bind:filters {reset} />
|
||||
<Sort />
|
||||
{:else}
|
||||
{:else}
|
||||
<Products productsView={$productsView} {currentProduct} />
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<slot />
|
||||
</main>
|
||||
|
|
|
@ -1,44 +1,32 @@
|
|||
<script>
|
||||
import { browser } from '$app/env';
|
||||
import Grid from '$lib/Grid.svelte';
|
||||
import Feature from '$lib/Feature/Feature.svelte';
|
||||
import { products, productsView, tags, currentProduct, emotions } from '$lib/stores';
|
||||
import { browser } from '$app/env';
|
||||
import {normalize, parseSlug } from '$helpers';
|
||||
import { findProdFromParam, getProdParam, resetParams } from '$helpers';
|
||||
|
||||
export let data;
|
||||
|
||||
products.set(data.products);
|
||||
tags.set(data.tags);
|
||||
emotions.set(data.emotions)
|
||||
|
||||
productsView.set(data.products);
|
||||
const goToProduct = () => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const paramProd = params.get('product');
|
||||
tags.set(data.tags);
|
||||
emotions.set(data.emotions);
|
||||
|
||||
if (paramProd) {
|
||||
const foundProduct = $products.find(
|
||||
({ name }) => normalize(name) === normalize(parseSlug(paramProd))
|
||||
);
|
||||
if (foundProduct) {
|
||||
currentProduct.set(foundProduct);
|
||||
}
|
||||
} else {
|
||||
const url = new URL(window.location.origin);
|
||||
history.pushState({}, '', url);
|
||||
currentProduct.set({});
|
||||
}
|
||||
const load = () => {
|
||||
currentProduct.set(findProdFromParam(getProdParam(), $products));
|
||||
if (getProdParam() && JSON.stringify($currentProduct) === '{}') resetParams();
|
||||
};
|
||||
|
||||
if (browser) {
|
||||
goToProduct();
|
||||
load();
|
||||
window.onpopstate = () => {
|
||||
goToProduct();
|
||||
}
|
||||
load();
|
||||
};
|
||||
}
|
||||
|
||||
const { container} = {
|
||||
const { container } = {
|
||||
container: 'h-screen overflow-auto w-screen'
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.8 KiB |
|
@ -4,7 +4,7 @@ import { configDefaults } from 'vitest/config';
|
|||
|
||||
/** @type {import('vite').UserConfig} */
|
||||
const config = {
|
||||
plugins: [sveltekit()],
|
||||
plugins: [sveltekit()],
|
||||
resolve: {
|
||||
alias: {
|
||||
$lib: resolve('./src/lib'),
|
||||
|
|
Loading…
Reference in New Issue