Merge branch 'dev' into 23-implement-unit-testing-helper-functions

This commit is contained in:
Zane Schaffer 2022-07-22 12:11:47 -07:00
commit 9bb2689989
16 changed files with 165 additions and 50 deletions

View File

@ -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;

View File

@ -1,4 +1,4 @@
export * from './toProduct';
export * from './parse';
export * from './normalize';
export * from './getAvgRating';
export * from './getAvgRating';
export * from './param';

View File

@ -1,2 +0,0 @@
export const normalize = (str) => str.toLowerCase().trim()

36
src/helpers/param.js Normal file
View File

@ -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;
};

View File

@ -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(" ","-")
}

View File

@ -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()

View File

@ -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>

View File

@ -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>

View File

@ -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'

View File

@ -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>

View File

@ -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`}
>

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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'),