Merge branch 'dev'

This commit is contained in:
2023-02-10 19:02:15 +01:00
117 changed files with 922 additions and 768 deletions

View File

@@ -0,0 +1,15 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
*.d.ts
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

View File

@@ -1,20 +1 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
plugins: ['svelte3', '@typescript-eslint'],
ignorePatterns: ['*.cjs'],
overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
settings: {
'svelte3/typescript': () => require('typescript')
},
parserOptions: {
sourceType: 'module',
ecmaVersion: 2019
},
env: {
browser: true,
es2017: true,
node: true
}
}
module.exports = require('config/eslintrc.config.cjs')

View File

@@ -20,26 +20,27 @@
"embla-carousel": "^7.0.9",
"focus-visible": "^5.2.0",
"motion": "^10.15.5",
"ogl": "^0.0.104",
"ogl": "^0.0.110",
"sanitize.css": "^13.0.0",
"swell-js": "3.19.5",
"tweakpane": "^3.1.4"
"swell-js": "3.19.8",
"tweakpane": "^3.1.4",
"utils": "workspace:*"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^1.0.2",
"@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/adapter-cloudflare": "^2.0.1",
"@sveltejs/adapter-node": "^1.1.4",
"@sveltejs/adapter-vercel": "^1.0.6",
"@sveltejs/kit": "^1.3.7",
"@typescript-eslint/eslint-plugin": "^5.50.0",
"@typescript-eslint/parser": "^5.50.0",
"@sveltejs/adapter-vercel": "^2.0.2",
"@sveltejs/kit": "^1.5.3",
"@typescript-eslint/eslint-plugin": "^5.51.0",
"@typescript-eslint/parser": "^5.51.0",
"base-64": "^1.0.0",
"browserslist": "^4.21.5",
"config": "workspace:*",
"cssnano": "^5.1.14",
"eslint": "^8.33.0",
"eslint-plugin-svelte3": "^4.0.0",
"eslint-plugin-svelte": "^2.16.0",
"postcss": "^8.4.21",
"postcss-focus-visible": "^8.0.1",
"postcss-focus-visible": "^8.0.2",
"postcss-normalize": "^10.0.1",
"postcss-preset-env": "^8.0.1",
"postcss-sort-media-queries": "^4.3.0",
@@ -49,7 +50,7 @@
"svelte-preprocess": "^5.0.1",
"tslib": "^2.5.0",
"typescript": "^4.9.5",
"vite": "^4.0.4"
"vite": "^4.1.1"
},
"type": "module",
"browserslist": [

View File

@@ -1,12 +1,11 @@
const cssnano = require('cssnano')
const presetEnv = require('postcss-preset-env')
const focusVisible = require('postcss-focus-visible')
// const sortMediaQueries = require('postcss-sort-media-queries')
const normalize = require('postcss-normalize')
import cssnano from 'cssnano'
import presetEnv from 'postcss-preset-env'
import focusVisible from 'postcss-focus-visible'
// import sortMediaQueries from 'postcss-sort-media-queries'
import normalize from 'postcss-normalize'
const dev = process.env.NODE_ENV !== 'development'
module.exports = {
export default {
plugins: [
// Preset Env
presetEnv({
@@ -25,7 +24,7 @@ module.exports = {
normalize({}),
// CSS Nano
!dev && cssnano({
!process.env.DEV && cssnano({
preset: ['default', {
autoprefixer: true,
discardComments: { removeAll: true },

View File

@@ -2,7 +2,7 @@
import { page } from '$app/stores'
import { afterUpdate } from 'svelte'
import { fade } from 'svelte/transition'
import { scrollToTop } from '$utils/functions'
import { scrollToTop } from 'utils/scroll'
import { pageLoading } from '$utils/stores'
import { DELAY, DURATION } from '$utils/constants'

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import { splitText } from '$utils/functions'
import { splitText } from 'utils/text'
export let text: string
export let mode: string = undefined
@@ -17,7 +17,6 @@
<span class="text-split__line" aria-hidden={index === 1}>
{#each split as word, i}
<span class="word" style:--i-w={i}>{word}</span>{#if word.includes('\n')}<br>{/if}
<!-- svelte-ignore empty-block -->
{#if i < split.length - 1}{/if}
{/each}
</span>

View File

@@ -45,7 +45,6 @@
href={url} class={classes}
{target} {rel}
data-sveltekit-noscroll={isExternal || isProtocol ? 'off' : ''}
{disabled}
tabindex="0"
on:click
>

View File

@@ -10,7 +10,7 @@
export let ratio: number = undefined
export let alt: string
export let lazy = true
export let decoding: "auto" | "sync" | "async" = "auto"
export let decoding: 'auto' | 'sync' | 'async' = 'auto'
interface Sizes {
small?: { width?: number, height?: number }
@@ -50,16 +50,16 @@
$: imgSrc = id ? getAssetUrlKey(id, `${sizeKey}-small-jpg`) : src ? src : undefined
$: srcSet = {
// WebP
webp:
sizes ? [
webp: sizes
? [
`${getAssetUrlKey(id, `${sizeKey}-small-webp`)} 345w`,
sizes.medium ? `${getAssetUrlKey(id, `${sizeKey}-medium-webp`)} 768w` : null,
sizes.large ? `${getAssetUrlKey(id, `${sizeKey}-large-webp`)} 1280w` : null,
]
: [getAssetUrlKey(id, `${sizeKey}-webp`)],
// JPG
jpg:
sizes ? [
jpg: sizes
? [
`${getAssetUrlKey(id, `${sizeKey}-small-jpg`)} 345w`,
sizes.medium ? `${getAssetUrlKey(id, `${sizeKey}-medium-jpg`)} 768w` : null,
sizes.large ? `${getAssetUrlKey(id, `${sizeKey}-large-jpg`)} 1280w` : null,

View File

@@ -8,7 +8,7 @@
</style>
<script lang="ts">
import { map } from '$utils/functions'
import { map } from 'utils/math'
import reveal from '$animations/reveal'
export let tag: string

View File

@@ -4,7 +4,7 @@
<script lang="ts">
import { addToCart } from '$utils/functions/shop'
import { capitalizeFirstLetter } from '$utils/functions'
import { capitalizeFirstLetter } from 'utils/string'
// Components
import SplitText from '$components/SplitText.svelte'
import Button from '$components/atoms/Button.svelte'
@@ -97,7 +97,7 @@
{#if product.photos_preview.length}
<div class="poster-layout__images grid container">
{#each product.photos_preview.slice(0, 3) as { directus_files_id}, index}
{#each product.photos_preview.slice(0, 3) as { directus_files_id }, index}
<Image
class="image image--{index + 1} photo shadow-box-light"
id={directus_files_id.id}

View File

@@ -6,7 +6,7 @@
import { getContext } from 'svelte'
import { spring } from 'svelte/motion'
import dayjs from 'dayjs'
import { lerp } from '$utils/functions'
import { lerp } from 'utils/math'
import { PUBLIC_PREVIEW_COUNT } from '$env/static/public'
import { seenLocations } from '$utils/stores'
// Components
@@ -31,7 +31,7 @@
// Detect if location has new content
const seenLocationDate = dayjs(parsedSeenLocations[location.id])
const isLocationSeen = parsedSeenLocations?.hasOwnProperty(location.id)
const isLocationSeen = location.id in parsedSeenLocations
// Define if location is has new photos
if (seenLocationDate && isLocationSeen) {

View File

@@ -19,8 +19,9 @@
</script>
<div class="notification-cart shadow-small"
on:click={closeNotification}
transition:fly={{ y: 20, duration: 700, easing: quartOut }}
on:click={closeNotification}
on:keydown
>
<div class="notification-cart__left">
<img src={image} width={58} height={88} alt={title}>

View File

@@ -36,7 +36,7 @@
alt={image.title}
/>
{:else if video && video.mp4 && video.webm}
<video muted loop playsinline autoplay allow="autoplay">
<video muted loop playsinline autoplay>
<source type="video/mp4" src={getAssetUrlKey(video.mp4, 'step')} />
<source type="video/webm" src={getAssetUrlKey(video.webm, 'step')} />
<track kind="captions" />

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import { createEventDispatcher } from 'svelte'
interface Option {
value: string
@@ -28,7 +28,7 @@
/**
* When changing select value
*/
const handleChange = ({ target: { value }}: any) => {
const handleChange = ({ target: { value } }: any) => {
const option = options.find(option => option.value === value)
// Dispatch event to parent

View File

@@ -20,7 +20,7 @@
// Quick location change
const quickLocationChange = async ({ target: { value }}: any) => {
const quickLocationChange = async ({ target: { value } }: any) => {
const pathTo = `/shop/poster-${value}`
goto(pathTo, { replaceState: true, noScroll: true, keepFocus: true })

View File

@@ -10,7 +10,7 @@
// Components
import Icon from '$components/atoms/Icon.svelte'
const { settings: { switcher_links }}: any = getContext('global')
const { settings: { switcher_links } }: any = getContext('global')
let switcherEl: HTMLElement
let isOpen = false

View File

@@ -10,12 +10,12 @@
import { smoothScroll } from '$utils/stores'
import { cartOpen, cartData, cartAmount, cartIsUpdating } from '$utils/stores/shop'
import { initSwell, getCart, updateCartItem, removeCartItem } from '$utils/functions/shop'
import { sendEvent } from '$utils/analytics'
// Components
import Button from '$components/atoms/Button.svelte'
import Icon from '$components/atoms/Icon.svelte'
import CartItem from '$components/molecules/CartItem.svelte'
import ShopLocationSwitcher from '$components/molecules/ShopLocationSwitcher.svelte'
import { sendEvent } from '$utils/analytics';
// Block scroll if cart is open
@@ -140,7 +140,7 @@
text="Checkout"
color="pink"
size="small"
on:click={() => sendEvent('cartCheckout', { props: { amount: $cartAmount }})}
on:click={() => sendEvent('cartCheckout', { props: { amount: $cartAmount } })}
/>
</div>
{/if}
@@ -151,5 +151,6 @@
<div class="cart-overlay"
transition:fade={{ duration: 600, easing: quartOut }}
on:click={handleCloseCart}
on:keydown
/>
{/if}

View File

@@ -8,7 +8,7 @@
import SplitText from '$components/SplitText.svelte'
import SiteTitle from '$components/atoms/SiteTitle.svelte'
const { settings: { instagram, footer_links }}: any = getContext('global')
const { settings: { instagram, footer_links } }: any = getContext('global')
</script>

View File

@@ -7,7 +7,8 @@
import { fade, fly as flySvelte } from 'svelte/transition'
import { quartOut } from 'svelte/easing'
import { Globe, type Marker } from '$modules/globe'
import { getRandomItem, debounce } from '$utils/functions'
import { getRandomItem } from 'utils/array'
import { debounce } from 'utils/actions'
import { revealSplit } from '$animations/transitions'
// Components
import SplitText from '$components/SplitText.svelte'
@@ -31,7 +32,7 @@
const { continents, locations }: any = getContext('global')
const randomContinent: any = getRandomItem(continents)
const markers = locations.map(({ name, slug, country, coordinates: { coordinates }}): Marker => ({
const markers = locations.map(({ name, slug, country, coordinates: { coordinates } }): Marker => ({
name,
slug,
country: { ...country },

View File

@@ -8,7 +8,7 @@
import { quartOut } from 'svelte/easing'
import reveal from '$animations/reveal'
import { send, receive } from '$animations/crossfade'
import { throttle } from '$utils/functions'
import { throttle } from 'utils/actions'
import { sendEvent } from '$utils/analytics'
// Components
import Button from '$components/atoms/Button.svelte'
@@ -16,12 +16,12 @@
export let locations: any[]
const { continents, settings: { explore_list }}: any = getContext('global')
const { continents, settings: { explore_list } }: any = getContext('global')
// Continents filtering logic
let currentContinent: string = undefined
$: filteredLocations = locations.filter(({ country: { continent }}: any) => {
$: filteredLocations = locations.filter(({ country: { continent } }: any) => {
if (!currentContinent) {
// Show all locations by default
return true

View File

@@ -9,7 +9,7 @@
export let theme = 'default'
const { settings: { newsletter_text, newsletter_subtitle }}: any = getContext('global')
const { settings: { newsletter_text, newsletter_subtitle } }: any = getContext('global')
</script>
<div class="newsletter newsletter--{theme} shadow-box-dark">

View File

@@ -7,7 +7,7 @@
import EmblaCarousel, { type EmblaCarouselType } from 'embla-carousel'
// Components
import Poster from '$components/molecules/Poster.svelte'
import { debounce } from '$utils/functions'
import { debounce } from 'utils/actions'
export let posters: any = []

View File

@@ -63,7 +63,7 @@
<div class="shop__images">
{#if images}
<a href={enabled ? url : undefined} title="Visit our shop" data-sveltekit-noscroll>
{#each images as { directus_files_id: { id, title }}, index}
{#each images as { directus_files_id: { id, title } }, index}
<Image
class={index === currentImageIndex ? 'is-visible' : null}
{id}

View File

@@ -16,21 +16,10 @@ export class Globe {
this.markers = options.markers || []
this.zoom = 1.3075
// Calculate the current sun position from a given location
// const locations = [
// {
// lat: -37.840935,
// lng: 144.946457,
// tz: 'Australia/Melbourne',
// },
// {
// lat: 48.856614,
// lng: 2.3522219,
// tz: 'Europe/Paris',
// }
// ]
// const location = locations[1]
// const localDate = new Date(new Date().toLocaleString('en-US', { timeZone: location.tz }))
// Calculate local time for sun position
const date = new Date()
const localHour = date.getHours() + date.getTimezoneOffset() / 60
this.options.sunAngle = (localHour - 12) / 12
// Parameters
this.params = {
@@ -39,7 +28,6 @@ export class Globe {
enableMarkers: options.enableMarkers,
enableMarkersLinks: options.enableMarkersLinks,
sunAngle: options.sunAngle || 0,
sunAngleDelta: 1.8,
}
// Misc
@@ -121,11 +109,10 @@ export class Globe {
imgDark.src = this.options.mapFileDark
// Create light
const lightD = degToRad(7 * 360 / 24)
const sunPosition = new Vec3(
Math.cos(lightD),
Math.sin(lightD) * Math.sin(0),
Math.sin(lightD) * Math.cos(0)
Math.cos(this.params.sunAngle),
Math.sin(this.params.sunAngle) * Math.sin(0),
Math.sin(this.params.sunAngle) * Math.cos(0)
)
// Create program
@@ -362,11 +349,11 @@ export type Marker = {
/**
* Detect WebGL support
*/
function WebGLSupport () {
const WebGLSupport = (): boolean => {
try {
const canvas = document.createElement('canvas')
return !!window.WebGLRenderingContext && (canvas.getContext('webgl') || canvas.getContext('experimental-webgl'))
} catch(e) {
} catch (e) {
return false
}
}

View File

@@ -47,9 +47,10 @@ export const createPane = (ctx: any) => {
title: 'Misc',
})
// Sun position
misc.addInput(ctx.params, 'sunAngleDelta', {
label: 'Sun angle delta',
misc.addInput(ctx.params, 'sunAngle', {
label: 'Sun angle',
min: 0,
max: 2 * Math.PI,
max: 1,
step: 0.01,
})
}

View File

@@ -15,7 +15,7 @@
},
500: {
title: 'Server error',
message: "That is embarassing, the problem is on our side.",
message: 'That is embarassing, the problem is on our side.',
},
}
</script>

View File

@@ -3,6 +3,10 @@ import type { LayoutServerLoad } from './$types'
import { fetchAPI } from '$utils/api'
import { fetchSwell } from '$utils/functions/shopServer'
export const config = {
runtime: 'edge'
}
export const load = (async () => {
try {
// Get content from API
@@ -53,7 +57,7 @@ export const load = (async () => {
}
}`)
const { data: { shop, location, posters, settings }} = res
const { data: { shop, location, posters, settings } } = res
/**

View File

@@ -1,9 +1,13 @@
import { error } from '@sveltejs/kit'
import type { PageServerLoad } from './$types'
import { fetchAPI } from '$utils/api'
import { getRandomItem } from '$utils/functions'
import { getRandomItem } from 'utils/array'
import { fetchSwell } from '$utils/functions/shopServer'
export const config = {
runtime: 'edge'
}
export const load = (async ({ setHeaders }) => {
try {
// Get content from API

View File

@@ -3,6 +3,10 @@ import type { PageServerLoad } from './$types'
import { fetchAPI } from '$utils/api'
import { fetchSwell } from '$utils/functions/shopServer'
export const config = {
runtime: 'edge'
}
export const load = (async ({ params, setHeaders }) => {
try {
// Get content from API

View File

@@ -3,7 +3,7 @@
import { getContext } from 'svelte'
import { getAssetUrlKey } from '$utils/api'
import { shopCurrentProductSlug } from '$utils/stores/shop'
import { capitalizeFirstLetter } from '$utils/functions'
import { capitalizeFirstLetter } from 'utils/string'
// Components
import PageTransition from '$components/PageTransition.svelte'
import Metas from '$components/Metas.svelte'

View File

@@ -3,6 +3,10 @@ import type { PageServerLoad } from './$types'
import { PUBLIC_LIST_AMOUNT } from '$env/static/public'
import { fetchAPI, photoFields } from '$utils/api'
export const config = {
runtime: 'edge'
}
export const load = (async ({ params, setHeaders }) => {
try {
const { location: slug } = params
@@ -70,10 +74,10 @@ export const load = (async ({ params, setHeaders }) => {
}
}`)
const { data: { location: location, photos, total_published, product }} = res
const { data: { location: location, photos, total_published, product } } = res
if (!location.length || location.length && params.country !== location[0].country.slug) {
throw error(404, "This location is not available… yet!")
throw error(404, 'This location is not available… yet!')
}
setHeaders({ 'Cache-Control': 'public, max-age=1, stale-while-revalidate=604799' })

View File

@@ -91,7 +91,7 @@
}
}`,
})
const { data: { photos }} = await res.json()
const { data: { photos } } = await res.json()
if (photos) {
// Return new photos
@@ -219,7 +219,6 @@
/>
<PageTransition>
<main class="location-page">
<section class="location-page__intro grid" bind:this={introEl}>
@@ -241,7 +240,7 @@
<div class="info">
<p class="text-label">
Photos by
{#each location.credits as { credit_id: { name, website }}}
{#each location.credits as { credit_id: { name, website } }}
{#if website}
<a href={website} target="_blank" rel="noopener external">
{name}
@@ -302,7 +301,7 @@
location={location.name}
ratio={width / height}
date={date_taken}
index={(totalPhotos - index < 10) ? '0' : ''}{totalPhotos - index}
index="{(totalPhotos - index < 10) ? '0' : ''}{totalPhotos - index}"
/>
{/each}
</section>
@@ -312,7 +311,7 @@
ended={ended}
current={currentPhotosAmount}
total={totalPhotos}
on:click={!ended && loadMorePhotos}
on:click={() => !ended && loadMorePhotos()}
>
{#if !ended}
<p class="more">See more photos</p>

View File

@@ -2,6 +2,10 @@ import { error } from '@sveltejs/kit'
import type { PageServerLoad } from './$types'
import { fetchAPI } from '$utils/api'
export const config = {
runtime: 'edge'
}
export const load = (async ({ params, setHeaders }) => {
try {
// Get the first photo ID

View File

@@ -15,7 +15,7 @@
import { getAssetUrlKey } from '$utils/api'
import { previousPage } from '$utils/stores'
import { DELAY } from '$utils/constants'
import { throttle } from '$utils/functions'
import { throttle } from 'utils/actions'
import { swipe } from '$utils/interactions/swipe'
// Components
import Metas from '$components/Metas.svelte'
@@ -93,10 +93,10 @@
const handleKeydown = ({ key, defaultPrevented }: KeyboardEvent) => {
if (defaultPrevented) return
switch (key) {
case 'ArrowLeft': goToPrevious(); break;
case 'ArrowRight': goToNext(); break;
case 'Escape': closeViewer(); break;
default: return;
case 'ArrowLeft': goToPrevious(); break
case 'ArrowRight': goToNext(); break
case 'Escape': closeViewer(); break
default: return
}
}
@@ -105,15 +105,15 @@
// Swipe up and down on mobile/small screens
if (innerWidth < 992) {
switch (detail) {
case '-y': goToNext(); break;
case 'y': goToPrevious(); break;
case '-y': goToNext(); break
case 'y': goToPrevious(); break
}
}
// Swipe left and right on larger screens
else {
switch (detail) {
case '-x': goToNext(); break;
case 'x': goToPrevious(); break;
case '-x': goToNext(); break
case 'x': goToPrevious(); break
}
}
}
@@ -174,7 +174,7 @@
}
}`,
})
const { data: { photos: newPhotos }} = await res.json()
const { data: { photos: newPhotos } } = await res.json()
// Not loading anymore
isLoading = false
@@ -320,7 +320,11 @@
</ButtonCircle>
<div class="photo-page__carousel">
<div class="photo-page__images" use:swipe on:swipe={handleSwipe} on:tap={toggleFullscreen}>
<div class="photo-page__images"
use:swipe
on:swipe={handleSwipe}
on:tap={toggleFullscreen}
>
{#each visiblePhotos as { id, image, title }, index (id)}
<div class="photo-page__picture is-{currentIndex === 0 ? index + 1 : index}">
<Image
@@ -377,7 +381,8 @@
</div>
{#if isFullscreen}
<div class="photo-page__fullscreen" bind:this={fullscreenEl} on:click={toggleFullscreen}
<div class="photo-page__fullscreen" bind:this={fullscreenEl}
on:click={toggleFullscreen} on:keydown
in:fade={{ easing: quartOut, duration: 1000 }}
out:fade={{ easing: quartOut, duration: 1000, delay: 300 }}
>

View File

@@ -1,7 +1,11 @@
import { error } from '@sveltejs/kit'
import type { PageServerLoad } from './$types'
import { fetchAPI } from '$utils/api'
import { getRandomItems } from '$utils/functions'
import { getRandomItems } from 'utils/array'
export const config = {
runtime: 'edge'
}
export const load = (async ({ setHeaders }) => {
try {
@@ -70,7 +74,7 @@ export const load = (async ({ setHeaders }) => {
seo_image { id }
}
}`)
const { data: { about, photos: photosIds }} = res
const { data: { about, photos: photosIds } } = res
// Get random photos
const randomPhotosIds = [...getRandomItems(photosIds, 42)].map(({ id }) => id)
@@ -89,7 +93,7 @@ export const load = (async ({ setHeaders }) => {
}`)
if (photosRes) {
const { data: { photo: photos }} = photosRes
const { data: { photo: photos } } = photosRes
setHeaders({ 'Cache-Control': 'public, max-age=1, stale-while-revalidate=604799' })

View File

@@ -3,15 +3,17 @@
</style>
<script lang="ts">
import { navigating, page } from '$app/stores'
import { navigating } from '$app/stores'
import { onMount, afterUpdate } from 'svelte'
import { quartOut as quartOutSvelte } from 'svelte/easing'
import { fade, fly } from 'svelte/transition'
import type { PageData } from './$types'
import { animate, inView, stagger, timeline } from 'motion'
import { mailtoClipboard, map } from '$utils/functions'
import { mailtoClipboard } from '$utils/functions'
import { getAssetUrlKey } from '$utils/api'
import { DELAY } from '$utils/constants'
import { map } from 'utils/math'
import { sendEvent } from '$utils/analytics'
import { quartOut } from '$animations/easings'
// Components
import Metas from '$components/Metas.svelte'
@@ -21,7 +23,6 @@
import AboutGridPhoto from '$components/atoms/AboutGridPhoto.svelte'
import ProcessStep from '$components/molecules/ProcessStep.svelte'
import Banner from '$components/organisms/Banner.svelte'
import { sendEvent } from '$utils/analytics';
export let data: PageData
const { about, photos } = data
@@ -223,7 +224,7 @@
{about.intro_firstphoto_caption}<br>
in
<a href="/{about.intro_firstlocation.country.slug}/{about.intro_firstlocation.slug}" data-sveltekit-noscroll>
<img src="{getAssetUrlKey(about.intro_firstlocation.country.flag.id, 'square-small-jpg')}" width="32" height="32" alt="{about.intro_firstlocation.country.flag.title}">
<img src={getAssetUrlKey(about.intro_firstlocation.country.flag.id, 'square-small-jpg')} width="32" height="32" alt={about.intro_firstlocation.country.flag.title}>
<span>Naarm Australia (Melbourne)</span>
</a>
</figcaption>

View File

@@ -2,6 +2,10 @@ import { error } from '@sveltejs/kit'
import type { RequestHandler } from './$types'
import { fetchAPI } from '$utils/api'
export const config = {
runtime: 'edge'
}
export const POST = (async ({ request, setHeaders }) => {
try {
const body = await request.text()

View File

@@ -2,6 +2,10 @@ import { error } from '@sveltejs/kit'
import type { PageServerLoad } from './$types'
import { fetchAPI } from '$utils/api'
export const config = {
runtime: 'edge'
}
export const load = (async ({ setHeaders }) => {
try {
const res = await fetchAPI(`query {

View File

@@ -3,6 +3,10 @@ import type { RequestHandler } from './$types'
import { fetchSwell } from '$utils/functions/shopServer'
import { fetchAPI, getAssetUrlKey } from '$utils/api'
export const config = {
runtime: 'edge'
}
const gCategories = [
{
id: '61851d83cd16416c78a8e5ef',
@@ -47,7 +51,7 @@ export const GET = (async ({ url, setHeaders }) => {
slug: siteProduct.location.slug,
description: siteProduct.description,
price: product.price,
images: siteProduct.photos_product.map(({ directus_files_id: { id }}: any) => getAssetUrlKey(id, `product-large-jpg`)),
images: siteProduct.photos_product.map(({ directus_files_id: { id } }: any) => getAssetUrlKey(id, `product-large-jpg`)),
gCategory: category.value,
gType: category.type,
})

View File

@@ -0,0 +1,3 @@
export const config = {
runtime: 'edge'
}

View File

@@ -14,7 +14,7 @@
import Heading from '$components/molecules/Heading.svelte'
const { locations }: any = getContext('global')
const text = "Explore the globe to discover unique locations across the world"
const text = 'Explore the globe to discover unique locations across the world'
</script>
<Metas

View File

@@ -3,6 +3,10 @@ import type { PageServerLoad } from './$types'
import { fetchAPI } from '$utils/api'
import { PUBLIC_FILTERS_DEFAULT_COUNTRY, PUBLIC_FILTERS_DEFAULT_SORT, PUBLIC_GRID_AMOUNT } from '$env/static/public'
export const config = {
runtime: 'edge'
}
export const load = (async ({ url, setHeaders }) => {
try {
// Query parameters

View File

@@ -13,7 +13,8 @@
import relativeTime from 'dayjs/plugin/relativeTime'
import { stagger, timeline } from 'motion'
import { DELAY } from '$utils/constants'
import { map, lerp, throttle } from '$utils/functions'
import { map, lerp } from 'utils/math'
import { throttle } from 'utils/actions'
import { getAssetUrlKey } from '$utils/api'
import { quartOut } from '$animations/easings'
import { PUBLIC_FILTERS_DEFAULT_COUNTRY, PUBLIC_FILTERS_DEFAULT_SORT, PUBLIC_GRID_INCREMENT } from '$env/static/public'
@@ -193,7 +194,7 @@
}`,
})
const { data: { photos }} = await res.json()
const { data: { photos } } = await res.json()
if (photos) {
// Return new photos

View File

@@ -2,6 +2,10 @@ import { error } from '@sveltejs/kit'
import type { PageServerLoad } from './$types'
import { fetchAPI } from '$utils/api'
export const config = {
runtime: 'edge'
}
export const load = (async ({ setHeaders }) => {
try {
const res = await fetchAPI(`query {

View File

@@ -2,6 +2,10 @@ import { error } from '@sveltejs/kit'
import type { PageServerLoad } from './$types'
import { fetchAPI } from '$utils/api'
export const config = {
runtime: 'edge'
}
export const load = (async ({ setHeaders }) => {
try {
const res = await fetchAPI(`query {

View File

@@ -3,6 +3,9 @@ import type { LayoutServerLoad } from './$types'
import { fetchAPI } from '$utils/api'
import { PUBLIC_PREVIEW_COUNT } from '$env/static/public'
export const config = {
runtime: 'edge'
}
export const load = (async () => {
try {
@@ -88,7 +91,7 @@ export const load = (async () => {
}`)
if (res) {
const { data: { countPhotos, countLocations, countCountries, ...rest }} = res
const { data: { countPhotos, countLocations, countCountries, ...rest } } = res
return {
...rest,

View File

@@ -1,8 +1,11 @@
import { error } from '@sveltejs/kit'
import type { PageServerLoad } from './$types'
import { fetchAPI } from '$utils/api'
import { getRandomItems } from '$utils/functions'
import { getRandomItems } from 'utils/array'
export const config = {
runtime: 'edge'
}
export const load = (async ({ setHeaders }) => {
try {
@@ -18,7 +21,7 @@ export const load = (async ({ setHeaders }) => {
id
}
}`)
const { data: { photo: photosIds }} = totalRes
const { data: { photo: photosIds } } = totalRes
// Get random photos
const randomPhotosIds = [...getRandomItems(photosIds, 11)].map(({ id }) => id)
@@ -41,7 +44,7 @@ export const load = (async ({ setHeaders }) => {
image { id }
}
}`)
const { data: { photo: photos }} = photosRes
const { data: { photo: photos } } = photosRes
if (photos) {
setHeaders({ 'Cache-Control': 'public, max-age=1, stale-while-revalidate=599' })

View File

@@ -1,6 +1,10 @@
import { NEWSLETTER_API_TOKEN, NEWSLETTER_LIST_ID } from '$env/static/private'
import type { RequestHandler } from './$types'
export const config = {
runtime: 'edge'
}
export const POST = (async ({ request, fetch }) => {
const data: { email: string } = await request.json()
const { email } = data

View File

@@ -2,6 +2,9 @@ import { error } from '@sveltejs/kit'
import type { RequestHandler } from './$types'
import { fetchAPI } from '$utils/api'
export const config = {
runtime: 'edge'
}
export const GET = (async ({ url, setHeaders }) => {
try {
@@ -54,7 +57,7 @@ export const GET = (async ({ url, setHeaders }) => {
})),
// Products
...products.map(({ location: { slug }}) => ({
...products.map(({ location: { slug } }) => ({
path: `/shop/poster-${slug}`,
priority: 0.7,
frequency: 'monthly',

View File

@@ -15,12 +15,11 @@ const cached = build.concat(files);
.then(cache => cache.addAll(cached))
.then(() => {
// if you use typescript:
(self as unknown as ServiceWorkerGlobalScope).skipWaiting();
(self as unknown as ServiceWorkerGlobalScope).skipWaiting()
// self.skipWaiting();
})
);
}
);
)
});
// self.addEventListener(
// if you use typescript:
@@ -38,7 +37,7 @@ const cached = build.concat(files);
(self as unknown as ServiceWorkerGlobalScope).clients.claim()
// self.clients.claim()
})
);
)
})
@@ -110,5 +109,4 @@ async function fetchAndCache(request: Request) {
return cachedAsset || fetchAndCache(event.request)
})()
)
}
)
})

View File

@@ -1,150 +1,6 @@
import { sendEvent } from '$utils/analytics'
/**
* Debounce a function with a given amount of time
* @description For scrolling or other resource demanding behaviors
*/
export const debounce = (callback: (...args: any[]) => any, wait: number, immediate = false) => {
let timeout: ReturnType<typeof setTimeout> | number = 0
return (...args: any[]) => {
const callNow: boolean = immediate && !timeout
const next = () => callback(...args)
clearTimeout(timeout)
timeout = setTimeout(next, wait)
if (callNow) next()
}
}
/**
* Throttle a function by a given amount of time
* @description Throttling enforces a maximum number of times a function can be called over time, as in 'execute this function at most once every 100 milliseconds
*/
export const throttle = (fn: (...args: any[]) => any, delay: number) => {
let lastCall = 0
return (...args: unknown[]) => {
const now = performance.now()
if (now - lastCall < delay) return
lastCall = now
requestAnimationFrame(() => fn(...args))
}
}
/**
* Split text
* @description Split a string into words or characters
* @returns string[]
*/
export const splitText = (text: string, mode = 'words'): string[] => {
// Split by words
if (mode === 'words') {
const words = text
.replace(/\\n/g, '\n')
.replace(/\s+/g, m => m.includes('\n') ? '\n ' : ' ')
.trim()
.split(' ')
return words
}
// Split by chars
else if (mode === 'chars') {
const chars = Array.from(text).map(char => char === ' ' ? '\xa0' : char)
return chars
}
}
/**
* Capitalize first letter
*/
export const capitalizeFirstLetter = (string: string) => {
return string[0].toUpperCase() + string.slice(1)
}
/**
* Create a delay
*/
export const sleep = (milliseconds: number) => {
return new Promise(resolve => setTimeout(resolve, milliseconds))
}
/**
* Linear Interpolation
*/
export const lerp = (start: number, end: number, amount: number): number => {
return (1 - amount) * start + amount * end
}
/**
* Re-maps a number from one range to another
* @param value the incoming value to be converted
* @param start1 lower bound of the value's current range
* @param stop1 upper bound of the value's current range
* @param start2 lower bound of the value's target range
* @param stop2 upper bound of the value's target range
* @param [withinBounds] constrain the value to the newly mapped range
* @return remapped number
*/
export const map = (n: number, start1: number, stop1: number, start2: number, stop2: number, withinBounds: boolean): number => {
const value = (n - start1) / (stop1 - start1) * (stop2 - start2) + start2
if (!withinBounds) return value
if (start2 < stop2) {
return clamp(value, start2, stop2)
} else {
return clamp(value, stop2, start2)
}
}
/**
* Clamp a number
*/
export const clamp = (num: number, a: number, b: number) => {
return Math.max(Math.min(num, Math.max(a, b)), Math.min(a, b))
}
/**
* Return random elements from an array
*/
export const getRandomItems = <T> (array: T[], amount: number): T[] => {
const shuffled = array.slice()
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]
}
return shuffled.slice(0, amount)
}
/**
* Return a random element from an array
*/
export const getRandomItem = <T extends Array<unknown>> (array: T): T[0] => {
return getRandomItems(array, 1)[0]
}
/**
* Scroll back to top after page transition
*/
export const scrollToTop = (delay?: number) => {
const scroll = () => window.scrollTo(0,0)
if (delay && delay > 0) {
setTimeout(scroll, delay)
} else {
return scroll()
}
}
/**
* Copy mailto links to clipboard and show message
*/

View File

@@ -2,4 +2,4 @@
* Add tabindex outlines when navigating with keyboard
* focus-visible polyfill: adds classes when tabbing
*/
import 'focus-visible'
import 'focus-visible'

View File

@@ -18,7 +18,7 @@ const config = {
kit: {
adapter: adapter({
// edge: true,
edge: true,
}),
alias: {
$components: 'src/components',

View File

@@ -11,5 +11,6 @@
"skipLibCheck": true,
"sourceMap": true,
// "strict": true
}
},
"exclude": [],
}

View File

@@ -1,9 +1,10 @@
{
"name": "housesof",
"version": "2.0.0",
"version": "2.0.1",
"private": true,
"workspaces": [
"apps/*"
"apps/*",
"packages/*"
],
"scripts": {
"dev": "turbo run dev --parallel",
@@ -11,12 +12,12 @@
"lint": "turbo run lint"
},
"devDependencies": {
"prettier": "^2.8.3",
"turbo": "^1.7.1"
"prettier": "^2.8.4",
"turbo": "^1.7.4"
},
"type": "module",
"engines": {
"node": ">=16.0.0"
},
"packageManager": "^pnpm@7.0.0"
"packageManager": "^pnpm@7.27.0"
}

View File

@@ -0,0 +1,78 @@
/** @type {import('eslint').Linter.Config} */
module.exports = {
root: true,
env: {
browser: true,
es2017: true,
node: true,
},
parserOptions: {
sourceType: 'module',
ecmaVersion: 2020,
project: './*/tsconfig.json',
extraFileExtensions: ['.svelte']
},
parser: '@typescript-eslint/parser',
plugins: [
'@typescript-eslint'
],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:svelte/recommended',
],
ignorePatterns: [
'*.d.ts',
'**/build/**', '**/node_modules/**', '**/.svelte-kit/**', '**/dist/**'
],
overrides: [
{
'files': ['*.svelte'],
'parser': 'svelte-eslint-parser',
'parserOptions': {
'parser': '@typescript-eslint/parser'
}
},
],
rules: {
/* Javascript / Typescript
========================================= */
// Use spaces over tabs
'no-tabs': 'error',
indent: ['error', 4, { SwitchCase: 1 }],
// Use single quote in javascript
quotes: ['error', 'single', {
avoidEscape: true,
allowTemplateLiterals: true
}],
// Avoid semicolons
semi: ['error', 'never'],
// Avoid ==
eqeqeq: 'error',
// Avoid trailing spaces
'no-trailing-spaces': ['error', { skipBlankLines: true }],
// Add a space between curlies
'object-curly-spacing': ['error', 'always'],
// Require a line at end of file
'eol-last': ['error', 'always'],
// Allow ts exceptions
'@typescript-eslint/ban-ts-comment': ['error', {
'ts-ignore': false,
'ts-nocheck': false,
'ts-expect-error': 'allow-with-description',
}],
/* Svelte
========================================= */
// Use double quotes
'svelte/html-quotes': ['error', {
prefer: 'double',
dynamic: {
quoted: false,
avoidInvalidUnquotedInHTML: false,
}
}],
},
settings: {
},
}

View File

@@ -0,0 +1,5 @@
{
"name": "config",
"version": "0.0.0",
"private": true
}

29
packages/utils/actions.ts Normal file
View File

@@ -0,0 +1,29 @@
/**
* Debounce a function with a given amount of time
* @description For scrolling or other resource demanding behaviors
*/
export const debounce = (callback: (...args: any[]) => any, wait: number, immediate = false) => {
let timeout: ReturnType<typeof setTimeout> | number = 0
return (...args: any[]) => {
const callNow: boolean = immediate && !timeout
const next = () => callback(...args)
clearTimeout(timeout)
timeout = setTimeout(next, wait)
if (callNow) next()
}
}
/**
* Throttle a function by a given amount of time
* @description Throttling enforces a maximum number of times a function can be called over time, as in 'execute this function at most once every 100 milliseconds
*/
export const throttle = (fn: (...args: any[]) => any, delay: number) => {
let lastCall = 0
return (...args: unknown[]) => {
const now = performance.now()
if (now - lastCall < delay) return
lastCall = now
requestAnimationFrame(() => fn(...args))
}
}

19
packages/utils/array.ts Normal file
View File

@@ -0,0 +1,19 @@
/**
* Return random elements from an array
*/
export const getRandomItems = <T> (array: T[], amount: number): T[] => {
const shuffled = array.slice()
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]
}
return shuffled.slice(0, amount)
}
/**
* Return a random element from an array
*/
export const getRandomItem = <T extends Array<unknown>> (array: T): T[0] => {
return getRandomItems(array, 1)[0]
}

17
packages/utils/index.ts Normal file
View File

@@ -0,0 +1,17 @@
/**
* Create a delay
*/
export const sleep = (milliseconds: number) => {
return new Promise(resolve => setTimeout(resolve, milliseconds))
}
/**
* Check if an object is empty
*/
export const isEmpty = (obj: Object) => {
if (obj === null || obj === undefined) {
throw new Error('Error: Given object is not an object')
}
return Object.keys(obj).length === 0 && obj.constructor === Object
}

35
packages/utils/math.ts Normal file
View File

@@ -0,0 +1,35 @@
/**
* Linear Interpolation
*/
export const lerp = (start: number, end: number, amount: number): number => {
return (1 - amount) * start + amount * end
}
/**
* Clamp a number
*/
export const clamp = (num: number, a: number, b: number) => {
return Math.max(Math.min(num, Math.max(a, b)), Math.min(a, b))
}
/**
* Re-maps a number from one range to another
* @param value the incoming value to be converted
* @param start1 lower bound of the value's current range
* @param stop1 upper bound of the value's current range
* @param start2 lower bound of the value's target range
* @param stop2 upper bound of the value's target range
* @param [withinBounds] constrain the value to the newly mapped range
* @return remapped number
*/
export const map = (n: number, start1: number, stop1: number, start2: number, stop2: number, withinBounds: boolean): number => {
const value = (n - start1) / (stop1 - start1) * (stop2 - start2) + start2
if (!withinBounds) return value
if (start2 < stop2) {
return clamp(value, start2, stop2)
} else {
return clamp(value, stop2, start2)
}
}

View File

@@ -0,0 +1,6 @@
{
"name": "utils",
"version": "0.0.0",
"private": true,
"module": "index.ts"
}

12
packages/utils/scroll.ts Normal file
View File

@@ -0,0 +1,12 @@
/**
* Scroll back to top after page transition
*/
export const scrollToTop = (delay?: number) => {
const scroll = () => window.scrollTo(0,0)
if (delay && delay > 0) {
return setTimeout(scroll, delay)
} else {
return scroll()
}
}

6
packages/utils/string.ts Normal file
View File

@@ -0,0 +1,6 @@
/**
* Capitalize first letter
*/
export const capitalizeFirstLetter = (string: string) => {
return string[0].toUpperCase() + string.slice(1)
}

24
packages/utils/text.ts Normal file
View File

@@ -0,0 +1,24 @@
/**
* Split text
* @description Split a string into words or characters
* @returns string[]
*/
export const splitText = (text: string, mode = 'words'): string[] => {
// Split by words
if (mode === 'words') {
const words = text
.replace(/\\n/g, '\n')
.replace(/\s+/g, m => m.includes('\n') ? '\n ' : ' ')
.trim()
.split(' ')
return words
}
// Split by chars
else if (mode === 'chars') {
const chars = Array.from(text).map(char => char === ' ' ? '\xa0' : char)
return chars
}
return []
}

737
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,2 +1,3 @@
packages:
- "apps/*"
- "packages/*"