☠️ RESET for v2
This commit is contained in:
57
src/routes/__layout.svelte
Normal file
57
src/routes/__layout.svelte
Normal file
@@ -0,0 +1,57 @@
|
||||
<script lang="ts">
|
||||
import { setContext } from 'svelte'
|
||||
// Other
|
||||
import '$utils/polyfills'
|
||||
import '../style/style.scss'
|
||||
|
||||
export let data: any
|
||||
|
||||
// Set global data
|
||||
setContext('global', data)
|
||||
|
||||
// console.log(data)
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
|
||||
<script context="module" lang="ts">
|
||||
import { fetchAPI } from '$utils/api'
|
||||
|
||||
export async function load ({ page, session, fetch, context }) {
|
||||
const res = await fetchAPI(`
|
||||
query {
|
||||
location {
|
||||
name
|
||||
slug
|
||||
country {
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
country {
|
||||
name
|
||||
slug
|
||||
}
|
||||
|
||||
continent {
|
||||
name
|
||||
slug
|
||||
}
|
||||
|
||||
settings {
|
||||
seo_name
|
||||
seo_title
|
||||
seo_description
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
const { data } = res
|
||||
|
||||
return {
|
||||
props: {
|
||||
data,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,49 +0,0 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte'
|
||||
import { site, pageReady } from 'utils/store'
|
||||
// Components
|
||||
import IconArrow from 'atoms/IconArrow'
|
||||
import TitleSite from 'atoms/TitleSite'
|
||||
import Button from 'atoms/Button'
|
||||
import Footer from 'organisms/Footer'
|
||||
|
||||
// Props
|
||||
export let status
|
||||
export let error
|
||||
|
||||
onMount(() => {
|
||||
pageReady.set(true)
|
||||
})
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Houses Of - {error.message}</title>
|
||||
</svelte:head>
|
||||
|
||||
{#if process.env.NODE_ENV === 'development' && error.stack}
|
||||
<div class="wrap">
|
||||
<pre>{error.stack}</pre>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<main class="housesof" class:is-transitioning={!$pageReady}>
|
||||
<section class="page">
|
||||
<div class="wrap">
|
||||
<div class="page__top">
|
||||
<a href="/" class="button-control button-control--lightpink dir-left" on:click|preventDefault={() => window.location = '/'}>
|
||||
<IconArrow direction="left" color="#fff" class="icon" />
|
||||
<IconArrow direction="left" color="#fff" class="icon" hidden="true" />
|
||||
</a>
|
||||
|
||||
<TitleSite />
|
||||
</div>
|
||||
|
||||
<div class="page__description style-description">
|
||||
<p>Oh no… Looks like something wrong just happened. Like a nasty error {status}.</p>
|
||||
<Button href="/" class="button" text="Go back to homepage" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
</section>
|
||||
</main>
|
||||
@@ -1,126 +0,0 @@
|
||||
<script context="module">
|
||||
import mainQuery from 'utils/mainQuery'
|
||||
|
||||
export async function preload (page, session) {
|
||||
await this.fetch(apiEndpoints.gql, {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'bearer ' + process.env.CONFIG.API_TOKEN
|
||||
},
|
||||
body: JSON.stringify({ query: mainQuery })
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
const { data } = res
|
||||
|
||||
// Manipulate countries data
|
||||
data.countries.data.forEach(country => {
|
||||
const matchingContinent = data.continents.data.find(continent => continent.id === country.continent.id)
|
||||
// Replace continent with request data
|
||||
country.continent = matchingContinent
|
||||
// Add countries to each continents
|
||||
matchingContinent.countries = []
|
||||
matchingContinent.countries.push(country)
|
||||
})
|
||||
|
||||
// Replace each location's country with request data
|
||||
data.locations.data.forEach(location => {
|
||||
location.country = data.countries.data.find(country => country.id === location.country.id)
|
||||
})
|
||||
|
||||
// Filter and keep only the latest photo for each location
|
||||
// https://stackoverflow.com/questions/61636965/
|
||||
const latestPhotos = Object.values(data.photos.data.reduce((photos, photo) => {
|
||||
if (photos.hasOwnProperty(photo.location.id)) {
|
||||
if (new Date(photos[photo.location.id].created_on) < new Date(photo.created_on)) {
|
||||
photos[photo.location.id] = photo
|
||||
}
|
||||
} else {
|
||||
photos[photo.location.id] = photo
|
||||
}
|
||||
return photos
|
||||
}, {}))
|
||||
|
||||
// Add last updated date to each location
|
||||
data.locations.data.forEach(location => {
|
||||
const latestPhoto = latestPhotos.find(photo => photo.location.id === location.id)
|
||||
if (latestPhoto) {
|
||||
location.last_updated = latestPhoto.created_on
|
||||
}
|
||||
})
|
||||
|
||||
// Set data into store
|
||||
site.set(data.site.data[0])
|
||||
continents.set(data.continents.data)
|
||||
countries.set(data.countries.data)
|
||||
locations.set(data.locations.data)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { onMount } from 'svelte'
|
||||
import { stores } from '@sapper/app'
|
||||
import {
|
||||
apiEndpoints,
|
||||
site,
|
||||
continents,
|
||||
countries,
|
||||
locations,
|
||||
pageReady
|
||||
} from 'utils/store'
|
||||
// Dependencies
|
||||
import lazySizes from 'lazysizes'
|
||||
// Components
|
||||
import AnalyticsTracker from 'utils/AnalyticsTracker'
|
||||
import Transition from 'utils/Transition'
|
||||
|
||||
// Variables
|
||||
const { page } = stores()
|
||||
|
||||
// Settings
|
||||
lazySizes.cfg.lazyClass = 'lazyload'
|
||||
|
||||
/*
|
||||
** Head stuff
|
||||
*/
|
||||
// Preconnect
|
||||
const preconnect = [
|
||||
'https://api.housesof.world',
|
||||
'https://www.googletagmanager.com',
|
||||
'https://stats.g.doubleclick.net',
|
||||
'https://www.google-analytics.com',
|
||||
]
|
||||
// Preload assets
|
||||
const preload = {
|
||||
fonts: [
|
||||
'/fonts/G-Light.woff2',
|
||||
'/fonts/G-Regular.woff2',
|
||||
'/fonts/G-Semibold.woff2',
|
||||
'/fonts/M-Extralight.woff2',
|
||||
'/fonts/M-Light.woff2',
|
||||
]
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" global>
|
||||
@import "../style/style.scss";
|
||||
</style>
|
||||
|
||||
<svelte:head>
|
||||
<link rel="canonical" href="{process.env.CONFIG.PROD_URL}{$page.path}">
|
||||
{#each preconnect as host}
|
||||
<link rel="preconnect" href={host} crossorigin>
|
||||
<link rel="dns-prefetch" href={host}>
|
||||
{/each}
|
||||
{#each preload.fonts as font}
|
||||
<link rel="preload" href={font} as="font" type="font/woff2" crossorigin>
|
||||
{/each}
|
||||
</svelte:head>
|
||||
|
||||
<slot />
|
||||
|
||||
<Transition />
|
||||
|
||||
<AnalyticsTracker {stores} id={process.env.CONFIG.GA_TRACKER_ID} />
|
||||
@@ -1,81 +0,0 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte'
|
||||
import { stores } from '@sapper/app'
|
||||
import {
|
||||
site,
|
||||
currentLocation,
|
||||
currentPhotos,
|
||||
pageReady,
|
||||
pageAnimation
|
||||
} from 'utils/store'
|
||||
// Dependencies
|
||||
import Lazy from 'svelte-lazy'
|
||||
// Components
|
||||
import IconArrow from 'atoms/IconArrow'
|
||||
import TitleSite from 'atoms/TitleSite'
|
||||
import InteractiveGlobe from 'molecules/InteractiveGlobe'
|
||||
import Locations from 'organisms/Locations'
|
||||
import Footer from 'organisms/Footer'
|
||||
import Transition from 'utils/Transition'
|
||||
import SocialMetas from 'utils/SocialMetas'
|
||||
// Animations
|
||||
import { animateIn } from 'animations/page'
|
||||
|
||||
// Variables
|
||||
const { page } = stores()
|
||||
pageAnimation.set(animateIn)
|
||||
const pageTitle = `${$site.seo_name} - ${$site.seo_title_default} across the globe`
|
||||
|
||||
// Reset current location
|
||||
currentLocation.set()
|
||||
currentPhotos.set()
|
||||
|
||||
|
||||
/*
|
||||
** Run code when mounted
|
||||
*/
|
||||
onMount(() => {
|
||||
// Page is loaded
|
||||
pageReady.set(true)
|
||||
})
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{pageTitle}</title>
|
||||
<meta name="description" content={$site.seo_description_default}>
|
||||
<SocialMetas
|
||||
url="{process.env.CONFIG.PROD_URL}{$page.path}"
|
||||
title="{pageTitle}"
|
||||
description={$site.seo_description_default}
|
||||
image={$site.seo_share_image.full_url}
|
||||
/>
|
||||
</svelte:head>
|
||||
|
||||
<main class="housesof" class:is-transitioning={!$pageReady}>
|
||||
<section class="page explore">
|
||||
<div class="wrap">
|
||||
<div class="page__top">
|
||||
<a href="/" class="button-control button-control--lightpink dir-left" aria-label="Back to homepage" rel="prefetch">
|
||||
<IconArrow direction="left" color="#fff" class="icon" />
|
||||
<IconArrow direction="left" color="#fff" class="icon" hidden="true" />
|
||||
</a>
|
||||
|
||||
<TitleSite />
|
||||
</div>
|
||||
|
||||
<div class="explore__description page__description page__part style-description">
|
||||
<p>{$site.explore_globe}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if process.browser}
|
||||
<Lazy offset={window.innerHeight} fadeOption={null}>
|
||||
<InteractiveGlobe />
|
||||
</Lazy>
|
||||
{/if}
|
||||
|
||||
<Locations />
|
||||
</section>
|
||||
|
||||
<Footer />
|
||||
</main>
|
||||
@@ -1,90 +0,0 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte'
|
||||
import { stores } from '@sapper/app'
|
||||
import { site, pageReady, pageAnimation } from 'utils/store'
|
||||
// Dependencies
|
||||
import Lazy from 'svelte-lazy'
|
||||
// Components
|
||||
import IconArrow from 'atoms/IconArrow'
|
||||
import TitleSite from 'atoms/TitleSite'
|
||||
import LinkTranslate from 'atoms/LinkTranslate'
|
||||
import InteractiveGlobe from 'molecules/InteractiveGlobe'
|
||||
import Footer from 'organisms/Footer'
|
||||
import SocialMetas from 'utils/SocialMetas'
|
||||
// Animations
|
||||
import { animateIn } from 'animations/page'
|
||||
|
||||
// Variables
|
||||
const { page } = stores()
|
||||
pageAnimation.set(animateIn)
|
||||
const pageTitle = `${$site.seo_name} - Credits`
|
||||
|
||||
|
||||
/*
|
||||
** Run code when mounted
|
||||
*/
|
||||
onMount(() => {
|
||||
// Page is loaded
|
||||
pageReady.set(true)
|
||||
})
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{pageTitle}</title>
|
||||
<meta name="description" content={$site.credits_text}>
|
||||
<SocialMetas
|
||||
url="{process.env.CONFIG.PROD_URL}{$page.path}"
|
||||
title="{pageTitle}"
|
||||
description={$site.credits_text}
|
||||
image={$site.seo_share_image.full_url}
|
||||
/>
|
||||
</svelte:head>
|
||||
|
||||
<main class="housesof page--credits" class:is-transitioning={!$pageReady}>
|
||||
<section class="page">
|
||||
<div class="wrap">
|
||||
<div class="page__top">
|
||||
<a href="/" class="button-control button-control--lightpink dir-left">
|
||||
<IconArrow direction="left" color="#fff" class="icon" />
|
||||
<IconArrow direction="left" color="#fff" class="icon" hidden="true" />
|
||||
</a>
|
||||
|
||||
<TitleSite />
|
||||
</div>
|
||||
|
||||
<div class="page__description page__part style-description">
|
||||
<p>{$site.credits_text}</p>
|
||||
</div>
|
||||
|
||||
{#if $site.credits_list}
|
||||
<div class="page__list page__part">
|
||||
{#each $site.credits_list as { title, credits }}
|
||||
<div class="page__category">
|
||||
<h2 class="title-category">{title}</h2>
|
||||
{#each credits as { name, role, website }, i}
|
||||
<dl>
|
||||
<dt class="style-location">
|
||||
{#if website}
|
||||
<LinkTranslate href={website} text={name} target="_blank" rel="noopener" />
|
||||
{:else}
|
||||
{name}
|
||||
{/if}
|
||||
</dt>
|
||||
<dd class="style-caps style-caps style-caps--transparent">{role}</dd>
|
||||
</dl>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if process.browser}
|
||||
<Lazy offset={window.innerHeight} fadeOption={null}>
|
||||
<InteractiveGlobe type="part" opacity="0.5" />
|
||||
</Lazy>
|
||||
{/if}
|
||||
|
||||
<Footer />
|
||||
</section>
|
||||
</main>
|
||||
@@ -1,137 +1,21 @@
|
||||
<script context="module">
|
||||
// Variables
|
||||
let limit = process.env.CONFIG.HOME_PHOTOS_LIMIT
|
||||
|
||||
// Preload data (photos to display)
|
||||
export async function preload (page, session) {
|
||||
// Load Carousel photos
|
||||
const fields = [
|
||||
'id', 'name', 'image.private_hash',
|
||||
'location.id', 'location.name', 'location.slug',
|
||||
'location.country.name', 'location.country.slug'
|
||||
]
|
||||
const sort = '?' // Random sort
|
||||
const req = await this.fetch(`${apiEndpoints.rest}/items/photos?fields=${fields.join()}&status=published&sort=${sort}&limit=${limit}`, {
|
||||
'Authorization': 'bearer ' + process.env.CONFIG.API_TOKEN
|
||||
})
|
||||
const photos = await req.json()
|
||||
if (req.ok) {
|
||||
return { photos: photos.data }
|
||||
}
|
||||
this.error(404, 'Not found')
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { onMount } from 'svelte'
|
||||
import { stores } from '@sapper/app'
|
||||
import {
|
||||
apiEndpoints,
|
||||
site,
|
||||
currentLocation,
|
||||
currentPhotos,
|
||||
pageReady,
|
||||
pageAnimation
|
||||
} from 'utils/store'
|
||||
import { charsToSpan, smoothScroll } from 'utils/functions'
|
||||
// Dependencies
|
||||
import Lazy from 'svelte-lazy'
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
// Components
|
||||
import Button from 'atoms/Button'
|
||||
import IconGlobeSmall from 'atoms/IconGlobeSmall'
|
||||
import InteractiveGlobe from 'molecules/InteractiveGlobe'
|
||||
import Carousel from 'organisms/Carousel'
|
||||
import Fullscreen from 'organisms/Fullscreen'
|
||||
import Locations from 'organisms/Locations'
|
||||
import Footer from 'organisms/Footer'
|
||||
import SocialMetas from 'utils/SocialMetas'
|
||||
// Animations
|
||||
import { animateIn } from 'animations/index'
|
||||
pageAnimation.set(animateIn)
|
||||
import Metas from '$components/Metas.svelte'
|
||||
import Locations from '$components/organisms/Locations.svelte'
|
||||
|
||||
// Props and Variables
|
||||
export let photos = ''
|
||||
const { page } = stores()
|
||||
let winWidth = 0
|
||||
|
||||
// Reset current location if existing
|
||||
currentLocation.set()
|
||||
currentPhotos.set()
|
||||
|
||||
|
||||
/*
|
||||
** Run code when mounted
|
||||
*/
|
||||
onMount(() => {
|
||||
// Page is loaded
|
||||
pageReady.set(true)
|
||||
})
|
||||
const globalData: any = getContext('global')
|
||||
console.log(globalData)
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{$site.seo_name} - {$site.seo_title_default} across the globe</title>
|
||||
<meta name="description" content={$site.seo_description_default}>
|
||||
<SocialMetas
|
||||
url="{process.env.CONFIG.PROD_URL}"
|
||||
title="{$site.seo_name} - {$site.seo_title_default} across the globe"
|
||||
description={$site.seo_description_default}
|
||||
image={$site.seo_share_image.full_url}
|
||||
/>
|
||||
</svelte:head>
|
||||
<Metas
|
||||
title="Houses Of"
|
||||
description=""
|
||||
image=""
|
||||
/>
|
||||
|
||||
<svelte:window bind:innerWidth={winWidth} />
|
||||
<h1>Houses Of</h1>
|
||||
|
||||
<main class="housesof" class:is-transitioning={!$pageReady}>
|
||||
<section class="intro">
|
||||
<div class="anim-mask">
|
||||
<div class="anim title-parallax" id="title-houses">
|
||||
<h1 class="title-massive" aria-label="Houses">
|
||||
{@html charsToSpan('Houses')}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wrap" id="intro-description">
|
||||
<div class="intro__description style-description">
|
||||
<p>{$site.description}</p>
|
||||
|
||||
<Button type="a" href="#choose" class="button" text="Explore locations" on:click={smoothScroll}>
|
||||
<IconGlobeSmall width="22" color="#666" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="intro-carousel">
|
||||
<Carousel {photos} locationUrl={true} />
|
||||
<Fullscreen />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="explore explore--homepage">
|
||||
<div class="of" id="title-of" role="heading" aria-level="2" aria-label="of">
|
||||
<div class="anim-mask">
|
||||
{@html charsToSpan('of')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="explore__description style-description" id="choose">
|
||||
<p>{$site.explore_globe}</p>
|
||||
</div>
|
||||
|
||||
{#if process.browser}
|
||||
<Lazy offset={window.innerHeight} fadeOption={null}>
|
||||
<InteractiveGlobe />
|
||||
</Lazy>
|
||||
{/if}
|
||||
|
||||
<div class="anim-mask anim-title">
|
||||
<h1 class="title-massive title-parallax" id="title-world" aria-label="World">
|
||||
{@html charsToSpan('World')}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<Locations />
|
||||
</section>
|
||||
|
||||
<Footer />
|
||||
</main>
|
||||
<Locations
|
||||
locations={globalData.location}
|
||||
/>
|
||||
@@ -1,209 +0,0 @@
|
||||
<script context="module">
|
||||
import { apiEndpoints } from 'utils/store'
|
||||
|
||||
// Preload data
|
||||
export async function preload (page, session) {
|
||||
// Load photos
|
||||
const fields = [
|
||||
'id', 'name', 'slug', 'date', 'image.private_hash',
|
||||
'location.id', 'location.name', 'location.slug', 'location.region',
|
||||
'location.country.name', 'location.country.slug', 'city',
|
||||
'created_on', 'modified_on'
|
||||
].join(',')
|
||||
const sort = ['-created_on', 'name'].join(',')
|
||||
const limit = -1
|
||||
const req = await this.fetch(`${apiEndpoints.rest}/items/photos?fields=${fields}&status=published&filter[location.slug][rlike]=%${page.params.place}%&limit=${limit}&sort=${sort}`, {
|
||||
'Authorization': 'bearer ' + process.env.CONFIG.API_TOKEN
|
||||
})
|
||||
const photos = await req.json()
|
||||
if (req.ok) {
|
||||
return { photos: photos.data }
|
||||
}
|
||||
this.error(404, 'Not found')
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { onMount } from 'svelte'
|
||||
import { stores } from '@sapper/app'
|
||||
import {
|
||||
site,
|
||||
locations,
|
||||
currentLocation,
|
||||
currentPhotos,
|
||||
pageReady,
|
||||
pageAnimation
|
||||
} from 'utils/store'
|
||||
import { formatDate, relativeTime, getThumbnail } from 'utils/functions'
|
||||
// Components
|
||||
import IconGlobe from 'atoms/IconGlobe'
|
||||
import IconGlobeSmall from 'atoms/IconGlobeSmall'
|
||||
import LinkChange from 'atoms/LinkChange'
|
||||
import ToggleLayout from 'atoms/ToggleLayout'
|
||||
import Photo from 'molecules/Photo'
|
||||
import Switcher from 'molecules/Switcher'
|
||||
import Pagination from 'organisms/Pagination'
|
||||
import Footer from 'organisms/Footer'
|
||||
import SocialMetas from 'utils/SocialMetas'
|
||||
// Animations
|
||||
import { animateIn } from 'animations/place'
|
||||
|
||||
// Props and variables
|
||||
export let photos
|
||||
const { page } = stores()
|
||||
pageAnimation.set(animateIn)
|
||||
let layoutSetting
|
||||
let windowWidth
|
||||
$: latestPhoto = photos[0]
|
||||
|
||||
// Update current location
|
||||
const location = $locations.find(loc => loc.slug === $page.params.place)
|
||||
const { description, country, illu_desktop, illu_desktop_2x, illu_mobile } = location
|
||||
currentLocation.set(location)
|
||||
currentPhotos.set(photos)
|
||||
|
||||
// Define dates
|
||||
$: latestPhotoModified = latestPhoto ? latestPhoto.modified_on.replace(' ', 'T') : ''
|
||||
$: dateUpdatedFull = latestPhoto ? formatDate(latestPhotoModified, 'FULL') : ''
|
||||
$: dateUpdatedDatetime = latestPhoto ? formatDate(latestPhotoModified, 'DATETIME') : ''
|
||||
$: dateUpdatedRelative = latestPhoto ? relativeTime(latestPhotoModified, 2592000000) : ''
|
||||
|
||||
|
||||
/*
|
||||
** Pagination
|
||||
*/
|
||||
let photosPerPage = $site.photos_per_page
|
||||
|
||||
// Hide photos by default
|
||||
photos.forEach((photo, index) => photo.hidden = (index + 1 > photosPerPage) ? true : false)
|
||||
let paginatedPhotos = photos.filter(photo => photo.hidden === false)
|
||||
|
||||
// Update pagination event from Pagination component
|
||||
const updatePagination = event => paginatedPhotos = event.detail
|
||||
|
||||
|
||||
/*
|
||||
** Run code when mounted
|
||||
*/
|
||||
onMount(() => {
|
||||
// Page is loaded
|
||||
pageReady.set(true)
|
||||
|
||||
// Get layout setting from storage
|
||||
layoutSetting = localStorage.getItem('photosLayout')
|
||||
})
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{$site.seo_name} – {$site.seo_title_default} of {location.name}, {country.name}</title>
|
||||
<meta name="description" content="{$site.seo_name} {location.name} {description}">
|
||||
<SocialMetas
|
||||
url="{process.env.CONFIG.PROD_URL}/location/{country.slug}/{location.slug}"
|
||||
title="{$site.seo_name} – {$site.seo_title_default} of {location.name}, {country.name}"
|
||||
description="{$site.seo_name} {location.name} {description}"
|
||||
image={latestPhoto ? getThumbnail(latestPhoto.image.private_hash, 1200, 630) : null}
|
||||
/>
|
||||
</svelte:head>
|
||||
|
||||
<svelte:window bind:innerWidth={windowWidth} />
|
||||
|
||||
<main class="housesof" class:is-transitioning={!$pageReady}>
|
||||
<section class="place">
|
||||
<div class="place__title">
|
||||
<h1 class="title-location title-location--big" aria-label="Houses of {location.name}" data-rellax-speed="-2.75" data-rellax-max-y="200">
|
||||
<span class="place__title_top anim-mask">
|
||||
<span class="place__title_houses">Houses</span>
|
||||
<em class="place__title_of">of</em>
|
||||
</span>
|
||||
<span class="place__title_bottom anim-mask">
|
||||
<span class="place__title_name">{location.name}</span>
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
<div data-rellax-speed={windowWidth <= 768 ? -2.5 : -1.75} data-rellax-max-y="200">
|
||||
<a href="/choose" class="button-control button-control--big button-control--dashed" aria-label="Change location">
|
||||
<span class="center">
|
||||
<IconGlobe width={windowWidth <= 768 ? 32 : 44} color="#fff" />
|
||||
<span>Change</span>
|
||||
</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
|
||||
<circle cx="50%" cy="50%" r="43%" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="place__wrap wrap">
|
||||
<div class="place__description">
|
||||
<div class="wrapper">
|
||||
<p>{$site.description}</p>
|
||||
|
||||
{#if description}
|
||||
<p>
|
||||
Houses Of
|
||||
<LinkChange href="/choose" text={location.name}>
|
||||
<IconGlobeSmall width="14" color="#999" />
|
||||
</LinkChange>
|
||||
{description}
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
{#if photos.length}
|
||||
<p class="updated style-caps">
|
||||
<strong>Updated</strong>
|
||||
<time datetime={dateUpdatedDatetime} title={dateUpdatedFull}>{dateUpdatedRelative}</time>
|
||||
</p>
|
||||
|
||||
<ToggleLayout />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if illu_desktop || illu_desktop_2x || illu_mobile}
|
||||
<div class="place__illustration">
|
||||
<div class="parallax" data-rellax-speed="-5" data-rellax-max-y="400">
|
||||
<div class="place__illustration_img"
|
||||
style="{`--url-desktop: ${illu_desktop ? `url(${illu_desktop.full_url});` : undefined}`}
|
||||
{`--url-desktop-2x: ${illu_desktop_2x ? `url(${illu_desktop_2x.full_url});` : undefined}`}
|
||||
{`--url-mobile: ${illu_mobile ? `url(${illu_mobile.full_url});` : undefined}`}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<section class="photos photos--{layoutSetting || 'list'}">
|
||||
<div class="photos__sidewrap wrap">
|
||||
<aside class="photos__side">
|
||||
<Switcher type="switcher--side" />
|
||||
|
||||
{#if photos.length}
|
||||
<p class="updated style-caps">
|
||||
<strong>Updated</strong>
|
||||
<time datetime={dateUpdatedDatetime} title={dateUpdatedFull}>{dateUpdatedRelative}</time>
|
||||
</p>
|
||||
{/if}
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
{#if photos}
|
||||
<div class="photos__view wrap">
|
||||
{#each paginatedPhotos as photo}
|
||||
<Photo {photo} index={photos.length - photos.indexOf(photo)} />
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<Pagination {photos} {paginatedPhotos} {photosPerPage}
|
||||
on:updatePagination={updatePagination}
|
||||
/>
|
||||
|
||||
{:else}
|
||||
<div class="wrap" style="padding-top: 20vw; padding-bottom: 20vw;">
|
||||
<p style="text-align: center; color: #333;">No photo for {location.name}</p>
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<Footer />
|
||||
</main>
|
||||
@@ -1,79 +0,0 @@
|
||||
const fs = require('fs')
|
||||
const fetch = require('node-fetch')
|
||||
import { apiEndpoints } from 'utils/store'
|
||||
import { formatDate } from 'utils/functions'
|
||||
|
||||
|
||||
// Variables
|
||||
let baseURL
|
||||
const pages = ['']
|
||||
|
||||
// Get routes and push it to array
|
||||
const routesExclude = ['sitemap', 'index', 'location', 'viewer']
|
||||
fs.readdirSync('./src/routes').forEach(file => {
|
||||
const filename = file.split('.')[0]
|
||||
if (!file.startsWith('.') && !filename.startsWith('_') && routesExclude.indexOf(filename) === -1) {
|
||||
pages.push(filename)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// Render function
|
||||
const render = (pages, locations) => {
|
||||
return `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<urlset
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
||||
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"
|
||||
>
|
||||
${pages.map(page => `
|
||||
<url>
|
||||
<loc>${baseURL}/${page}</loc>
|
||||
<priority>0.75</priority>
|
||||
</url>`).join('\n')}
|
||||
|
||||
${locations.map(loc => `
|
||||
<url>
|
||||
<loc>${baseURL}/location/${loc.country.slug}/${loc.slug}</loc>
|
||||
<priority>1</priority>
|
||||
<lastmod>${formatDate(loc.updated, 'DATETIME')}</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
</url>`).join('\n')
|
||||
}
|
||||
</urlset>`
|
||||
}
|
||||
|
||||
// Get function
|
||||
export async function get (req, res, next) {
|
||||
let locations
|
||||
|
||||
// Define base url from request
|
||||
baseURL = `https://${req.headers.host}`
|
||||
|
||||
// Get locations
|
||||
await fetch(`${apiEndpoints.rest}/items/locations?fields=slug,country.slug,modified_on&status=published&sort=created_on`)
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
locations = res.data
|
||||
})
|
||||
|
||||
// Add last modified date to each location from its last photo
|
||||
const updatedLocations = locations.map(async (location, i) => {
|
||||
await fetch(`${apiEndpoints.rest}/items/photos?fields=created_on&limit=1&sort=-created_on&status=published&filter[location.slug][rlike]=%${location.slug}`)
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
const latestPhoto = res.data[0]
|
||||
location.updated = latestPhoto ? latestPhoto.created_on : location.modified_on
|
||||
})
|
||||
return location
|
||||
})
|
||||
await Promise.all(updatedLocations)
|
||||
|
||||
// Set headers
|
||||
res.setHeader('Cache-Control', 'max-age=0, s-max-age=600') // 10 minutes
|
||||
res.setHeader('Content-Type', 'application/rss+xml')
|
||||
|
||||
// Render sitemap
|
||||
const sitemap = render(pages, locations)
|
||||
res.end(sitemap)
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte'
|
||||
import { stores } from '@sapper/app'
|
||||
import { site, pageReady, pageAnimation } from 'utils/store'
|
||||
// Dependencies
|
||||
import Lazy from 'svelte-lazy'
|
||||
// Components
|
||||
import IconArrow from 'atoms/IconArrow'
|
||||
import TitleSite from 'atoms/TitleSite'
|
||||
import LinkTranslate from 'atoms/LinkTranslate'
|
||||
import InteractiveGlobe from 'molecules/InteractiveGlobe'
|
||||
import NewsletterForm from 'molecules/NewsletterForm'
|
||||
import Footer from 'organisms/Footer'
|
||||
import SocialMetas from 'utils/SocialMetas'
|
||||
// Animations
|
||||
import { animateIn } from 'animations/page'
|
||||
|
||||
// Variables
|
||||
const { page } = stores()
|
||||
pageAnimation.set(animateIn)
|
||||
const pageTitle = `${$site.seo_name} - Keep Updated`
|
||||
|
||||
|
||||
/*
|
||||
** Run code when mounted
|
||||
*/
|
||||
onMount(() => {
|
||||
// Page is loaded
|
||||
pageReady.set(true)
|
||||
})
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{pageTitle}</title>
|
||||
<meta name="description" content={$site.subscribe_text}>
|
||||
<SocialMetas
|
||||
url="{process.env.CONFIG.PROD_URL}{$page.path}"
|
||||
title="{pageTitle}"
|
||||
description={$site.subscribe_text}
|
||||
image={$site.seo_share_image.full_url}
|
||||
/>
|
||||
</svelte:head>
|
||||
|
||||
<main class="housesof page--credits" class:is-transitioning={!$pageReady}>
|
||||
<section class="page">
|
||||
<div class="wrap">
|
||||
<div class="page__top">
|
||||
<a href="/" class="button-control button-control--lightpink dir-left">
|
||||
<IconArrow direction="left" color="#fff" class="icon" />
|
||||
<IconArrow direction="left" color="#fff" class="icon" hidden="true" />
|
||||
</a>
|
||||
|
||||
<TitleSite />
|
||||
</div>
|
||||
|
||||
<div class="newsletter newsletter--column">
|
||||
<div class="newsletter__text style-description page__part">
|
||||
<p>{$site.newsletter_text}</p>
|
||||
</div>
|
||||
|
||||
<div class="page__part">
|
||||
<NewsletterForm title={true} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if process.browser}
|
||||
<Lazy offset={window.innerHeight} fadeOption={null}>
|
||||
<InteractiveGlobe type="part" opacity="0.5" />
|
||||
</Lazy>
|
||||
{/if}
|
||||
|
||||
<Footer />
|
||||
</section>
|
||||
</main>
|
||||
@@ -1,137 +0,0 @@
|
||||
<script context="module">
|
||||
// Define either to preload data or use the store
|
||||
let preloaded
|
||||
currentPhotos.subscribe(store => preloaded = store ? store : undefined)
|
||||
|
||||
// Preload data
|
||||
export async function preload (page, session) {
|
||||
// Load the photos if not loaded
|
||||
if (!preloaded) {
|
||||
const fields = [
|
||||
'id', 'name', 'slug', 'date', 'image.private_hash',
|
||||
'location.id', 'location.name', 'location.slug',
|
||||
'location.country.name', 'location.country.slug'
|
||||
]
|
||||
const req = await this.fetch(`${apiEndpoints.rest}/items/photos?fields=${fields.join()}&status=published&filter[location.slug][rlike]=%${page.params.place}%`, {
|
||||
'Authorization': 'bearer ' + process.env.CONFIG.API_TOKEN
|
||||
})
|
||||
const photos = await req.json()
|
||||
if (req.ok) {
|
||||
return { photos: photos.data }
|
||||
}
|
||||
}
|
||||
// Use the store otherwise
|
||||
else return {
|
||||
photos: preloaded
|
||||
}
|
||||
this.error(404, 'Not found')
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { onMount } from 'svelte'
|
||||
import { stores } from '@sapper/app'
|
||||
import {
|
||||
apiEndpoints,
|
||||
site,
|
||||
locations,
|
||||
currentLocation,
|
||||
currentPhotos,
|
||||
pageReady,
|
||||
pageAnimation
|
||||
} from 'utils/store'
|
||||
import { getThumbnail } from 'utils/functions'
|
||||
// Components
|
||||
import IconGlobe from 'atoms/IconGlobe'
|
||||
import IconCross from 'atoms/IconCross'
|
||||
import Carousel from 'organisms/Carousel'
|
||||
import Fullscreen from 'organisms/Fullscreen'
|
||||
import Transition from 'utils/Transition'
|
||||
import SocialMetas from 'utils/SocialMetas'
|
||||
// Animations
|
||||
import { animateIn } from 'animations/viewer'
|
||||
|
||||
// Props
|
||||
export let photos
|
||||
|
||||
const { page } = stores()
|
||||
pageAnimation.set(animateIn)
|
||||
|
||||
// Update store current location
|
||||
currentLocation.set($locations.find(loc => loc.slug === $page.params.place))
|
||||
currentPhotos.set(photos)
|
||||
|
||||
let windowWidth
|
||||
let gotoLink
|
||||
let currentPhoto = photos.find(photo => photo.slug === $page.params.photo)
|
||||
|
||||
|
||||
// Photo has changed (from Carousel)
|
||||
const photoChanged = event => {
|
||||
const currentPhoto = event.detail
|
||||
const windowPathname = window.location.pathname
|
||||
if (currentPhoto) {
|
||||
const newUrl = windowPathname.substring(0, windowPathname.lastIndexOf('/') + 1) + currentPhoto.slug
|
||||
// Go to page via a sapper-noscroll link to avoid scroll jump (hacky)
|
||||
if (gotoLink && newUrl) {
|
||||
gotoLink.href = newUrl
|
||||
gotoLink.click()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Run code when mounted
|
||||
*/
|
||||
onMount(() => {
|
||||
// Page is loaded
|
||||
pageReady.set(true)
|
||||
})
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
{#if $currentLocation}
|
||||
<title>{$site.seo_name} – {$site.seo_title_default} of {$currentLocation.name}, {$currentLocation.country.name}</title>
|
||||
<meta name="description" content="{$site.seo_name} {$currentLocation.name} {$currentLocation.description}">
|
||||
<SocialMetas
|
||||
url="{process.env.CONFIG.PROD_URL}/viewer/{currentPhoto.location.country.slug}/{currentPhoto.location.slug}/{currentPhoto.slug}"
|
||||
title="{$site.seo_name} - {$site.seo_title_default} of {$currentLocation.name}, {$currentLocation.country.name}"
|
||||
description="{$site.seo_name} {$currentLocation.name} {$currentLocation.description}"
|
||||
image={getThumbnail(currentPhoto.image.private_hash, 1200, 630)}
|
||||
/>
|
||||
{/if}
|
||||
</svelte:head>
|
||||
|
||||
<svelte:window bind:innerWidth={windowWidth} />
|
||||
|
||||
<main class="housesof" class:is-transitioning={!$pageReady}>
|
||||
<section class="viewer">
|
||||
<div class="viewer__top">
|
||||
<p class="tip">Tap for fullscreen</p>
|
||||
|
||||
<div class="viewer__buttons">
|
||||
<a href="/choose" class="button-control button-control--dashed" aria-label="Change the location" rel="prefetch">
|
||||
<IconGlobe color="#fff" width={windowWidth >= 768 ? 22 : 18} />
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
|
||||
<circle cx="50%" cy="50%" r="{windowWidth >= 768 ? 32 : 24}px"></circle>
|
||||
</svg>
|
||||
</a>
|
||||
{#if $currentLocation}
|
||||
<a href="/location/{$currentLocation.country.slug}/{$currentLocation.slug}" class="button-control button-control--lightpink dir-bottom" aria-label="Back to photos" rel="prefetch">
|
||||
<IconCross color="#fff" width="18" class="icon" />
|
||||
<IconCross color="#fff" width="18" class="icon" hidden="true" />
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<a href="/" bind:this={gotoLink} aria-hidden="true" hidden class="hidden" sapper-noscroll> </a>
|
||||
</div>
|
||||
|
||||
<Carousel {photos} viewer={true}
|
||||
on:photoChange={photoChanged}
|
||||
/>
|
||||
|
||||
<Fullscreen />
|
||||
</section>
|
||||
</main>
|
||||
Reference in New Issue
Block a user