Add Fullscreen in viewer, Track links with Google Analytics, Use .env file
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
- Fullscreen is a component that watches a store value set by the Carousel component on a picture click - Use a .env file for API and website related settings and informations - Google Analytics is now in place, tracking each routes link and viewer photo change
This commit is contained in:
10
.env
Normal file
10
.env
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Website
|
||||||
|
PROD_URL=https://housesof.world
|
||||||
|
|
||||||
|
# API
|
||||||
|
API_TOKEN=NJk0urljsdSvApUDzWxGgoO6
|
||||||
|
API_URL_DEV=http://api.housesof.localhost/how
|
||||||
|
API_URL_PROD=https://api.housesof.world/_
|
||||||
|
|
||||||
|
# Tracking
|
||||||
|
GA_TRACKER_ID=UA-4060922-27
|
||||||
@@ -37,6 +37,7 @@
|
|||||||
"@rollup/plugin-replace": "^2.3.1",
|
"@rollup/plugin-replace": "^2.3.1",
|
||||||
"autoprefixer": "^9.7.5",
|
"autoprefixer": "^9.7.5",
|
||||||
"babel-plugin-module-resolver": "^4.0.0",
|
"babel-plugin-module-resolver": "^4.0.0",
|
||||||
|
"dotenv": "^8.2.0",
|
||||||
"eslint-config-standard": "^14.1.1",
|
"eslint-config-standard": "^14.1.1",
|
||||||
"eslint-plugin-import": "^2.20.1",
|
"eslint-plugin-import": "^2.20.1",
|
||||||
"eslint-plugin-node": "^11.0.0",
|
"eslint-plugin-node": "^11.0.0",
|
||||||
|
|||||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@@ -19,6 +19,7 @@ devDependencies:
|
|||||||
'@rollup/plugin-replace': 2.3.1_rollup@2.2.0
|
'@rollup/plugin-replace': 2.3.1_rollup@2.2.0
|
||||||
autoprefixer: 9.7.5
|
autoprefixer: 9.7.5
|
||||||
babel-plugin-module-resolver: 4.0.0
|
babel-plugin-module-resolver: 4.0.0
|
||||||
|
dotenv: 8.2.0
|
||||||
eslint-config-standard: 14.1.1_13a54f81caffeb9134dc06c172bdde71
|
eslint-config-standard: 14.1.1_13a54f81caffeb9134dc06c172bdde71
|
||||||
eslint-plugin-import: 2.20.1
|
eslint-plugin-import: 2.20.1
|
||||||
eslint-plugin-node: 11.0.0
|
eslint-plugin-node: 11.0.0
|
||||||
@@ -1649,6 +1650,12 @@ packages:
|
|||||||
node: '>=6.0.0'
|
node: '>=6.0.0'
|
||||||
resolution:
|
resolution:
|
||||||
integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==
|
integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==
|
||||||
|
/dotenv/8.2.0:
|
||||||
|
dev: true
|
||||||
|
engines:
|
||||||
|
node: '>=8'
|
||||||
|
resolution:
|
||||||
|
integrity: sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==
|
||||||
/ecc-jsbn/0.1.2:
|
/ecc-jsbn/0.1.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
jsbn: 0.1.1
|
jsbn: 0.1.1
|
||||||
@@ -4574,6 +4581,7 @@ specifiers:
|
|||||||
autoprefixer: ^9.7.5
|
autoprefixer: ^9.7.5
|
||||||
babel-plugin-module-resolver: ^4.0.0
|
babel-plugin-module-resolver: ^4.0.0
|
||||||
compression: ^1.7.4
|
compression: ^1.7.4
|
||||||
|
dotenv: ^8.2.0
|
||||||
eslint-config-standard: ^14.1.1
|
eslint-config-standard: ^14.1.1
|
||||||
eslint-plugin-import: ^2.20.1
|
eslint-plugin-import: ^2.20.1
|
||||||
eslint-plugin-node: ^11.0.0
|
eslint-plugin-node: ^11.0.0
|
||||||
|
|||||||
@@ -7,13 +7,18 @@ import babel from 'rollup-plugin-babel'
|
|||||||
// import browsersync from 'rollup-plugin-browsersync'
|
// import browsersync from 'rollup-plugin-browsersync'
|
||||||
import autoPreprocess from 'svelte-preprocess'
|
import autoPreprocess from 'svelte-preprocess'
|
||||||
import { terser } from 'rollup-plugin-terser'
|
import { terser } from 'rollup-plugin-terser'
|
||||||
import config from 'sapper/config/rollup'
|
import sapperConfig from 'sapper/config/rollup'
|
||||||
|
import { config } from 'dotenv'
|
||||||
import pkg from './package.json'
|
import pkg from './package.json'
|
||||||
|
|
||||||
// Define environment and things
|
// Define environment and things
|
||||||
const mode = process.env.NODE_ENV
|
const mode = process.env.NODE_ENV
|
||||||
const dev = mode === 'development'
|
const dev = mode === 'development'
|
||||||
const legacy = !!process.env.SAPPER_LEGACY_BUILD
|
const legacy = !!process.env.SAPPER_LEGACY_BUILD
|
||||||
|
const replaceOptions = {
|
||||||
|
'process.env.NODE_ENV': JSON.stringify(mode),
|
||||||
|
'process.env.CONFIG': JSON.stringify(config().parsed)
|
||||||
|
}
|
||||||
|
|
||||||
// Svelte
|
// Svelte
|
||||||
const onwarn = (warning, onwarn) => (warning.code === 'CIRCULAR_DEPENDENCY' && /[/\\]@sapper[/\\]/.test(warning.message)) || onwarn(warning)
|
const onwarn = (warning, onwarn) => (warning.code === 'CIRCULAR_DEPENDENCY' && /[/\\]@sapper[/\\]/.test(warning.message)) || onwarn(warning)
|
||||||
@@ -33,24 +38,20 @@ export default {
|
|||||||
** Client
|
** Client
|
||||||
*/
|
*/
|
||||||
client: {
|
client: {
|
||||||
input: config.client.input(),
|
input: sapperConfig.client.input(),
|
||||||
output: {
|
output: sapperConfig.client.output(),
|
||||||
...config.client.output(),
|
|
||||||
// ...dev && { exports: 'named' }
|
|
||||||
},
|
|
||||||
// experimentalCodeSplitting: true,
|
|
||||||
plugins: [
|
plugins: [
|
||||||
// Javascript
|
// Javascript
|
||||||
replace({
|
replace({
|
||||||
'process.browser': true,
|
'process.browser': true,
|
||||||
'process.env.NODE_ENV': JSON.stringify(mode)
|
...replaceOptions
|
||||||
}),
|
}),
|
||||||
svelte({
|
svelte({
|
||||||
dev,
|
dev,
|
||||||
|
preprocess,
|
||||||
hydratable: true,
|
hydratable: true,
|
||||||
emitCss: true,
|
emitCss: true,
|
||||||
// css: css => css.write('static/bundle.css'),
|
// css: css => css.write('static/bundle.css')
|
||||||
preprocess
|
|
||||||
}),
|
}),
|
||||||
resolve({
|
resolve({
|
||||||
browser: true,
|
browser: true,
|
||||||
@@ -79,17 +80,17 @@ export default {
|
|||||||
** Server
|
** Server
|
||||||
*/
|
*/
|
||||||
server: {
|
server: {
|
||||||
input: config.server.input(),
|
input: sapperConfig.server.input(),
|
||||||
output: config.server.output(),
|
output: sapperConfig.server.output(),
|
||||||
plugins: [
|
plugins: [
|
||||||
replace({
|
replace({
|
||||||
'process.browser': false,
|
'process.browser': false,
|
||||||
'process.env.NODE_ENV': JSON.stringify(mode)
|
...replaceOptions
|
||||||
}),
|
}),
|
||||||
svelte({
|
svelte({
|
||||||
dev,
|
dev,
|
||||||
generate: 'ssr',
|
preprocess,
|
||||||
preprocess
|
generate: 'ssr'
|
||||||
}),
|
}),
|
||||||
resolve({
|
resolve({
|
||||||
browser: true,
|
browser: true,
|
||||||
@@ -101,7 +102,6 @@ export default {
|
|||||||
external: Object.keys(pkg.dependencies).concat(
|
external: Object.keys(pkg.dependencies).concat(
|
||||||
require('module').builtinModules || Object.keys(process.binding('natives'))
|
require('module').builtinModules || Object.keys(process.binding('natives'))
|
||||||
),
|
),
|
||||||
|
|
||||||
onwarn,
|
onwarn,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -110,13 +110,13 @@ export default {
|
|||||||
** Service worker
|
** Service worker
|
||||||
*/
|
*/
|
||||||
// serviceworker: {
|
// serviceworker: {
|
||||||
// input: config.serviceworker.input(),
|
// input: sapperConfig.serviceworker.input(),
|
||||||
// output: config.serviceworker.output(),
|
// output: sapperConfig.serviceworker.output(),
|
||||||
// plugins: [
|
// plugins: [
|
||||||
// resolve(),
|
// resolve(),
|
||||||
// replace({
|
// replace({
|
||||||
// 'process.browser': true,
|
// 'process.browser': true,
|
||||||
// 'process.env.NODE_ENV': JSON.stringify(mode)
|
// ...replaceOptions
|
||||||
// }),
|
// }),
|
||||||
// commonjs(),
|
// commonjs(),
|
||||||
// !dev && terser()
|
// !dev && terser()
|
||||||
|
|||||||
9
src/atoms/IconZoomOut.svelte
Normal file
9
src/atoms/IconZoomOut.svelte
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<script>
|
||||||
|
export let width = 18
|
||||||
|
export let color = '#fff'
|
||||||
|
export let hidden = undefined
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19 18" {width} fill={color} class={$$props.class} aria-hidden={hidden}>
|
||||||
|
<path fill-rule="evenodd" d="M8.36 0a7.76 7.76 0 016.02 12.63l3.99 3.99a.81.81 0 11-1.15 1.14l-3.99-3.99A7.76 7.76 0 118.35 0zm0 1.62a6.14 6.14 0 10.01 12.28 6.14 6.14 0 00-.01-12.28zm2.38 5.32a.81.81 0 110 1.62H5.98a.81.81 0 110-1.62z"/>
|
||||||
|
</svg>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onMount, createEventDispatcher } from 'svelte'
|
import { onMount, createEventDispatcher } from 'svelte'
|
||||||
import { stores } from '@sapper/app'
|
import { stores } from '@sapper/app'
|
||||||
import { currentLocation } from '../utils/store'
|
import { currentLocation, fullscreen } from '../utils/store'
|
||||||
import { getThumbnail, formatDate } from '../utils/functions'
|
import { getThumbnail, formatDate } from '../utils/functions'
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const { page } = stores()
|
const { page } = stores()
|
||||||
@@ -50,6 +50,8 @@
|
|||||||
|
|
||||||
// Dispatch current photo
|
// Dispatch current photo
|
||||||
sendCurrentPhoto()
|
sendCurrentPhoto()
|
||||||
|
// Reset fullscreen value if open
|
||||||
|
fullscreen.set()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send current photo to event
|
// Send current photo to event
|
||||||
@@ -145,6 +147,7 @@
|
|||||||
class:gallery__photo--prev={photo === prevPhoto}
|
class:gallery__photo--prev={photo === prevPhoto}
|
||||||
class:gallery__photo--active={photo === currentPhoto}
|
class:gallery__photo--active={photo === currentPhoto}
|
||||||
class:gallery__photo--next={photo === nextPhoto}
|
class:gallery__photo--next={photo === nextPhoto}
|
||||||
|
on:click={event => viewer && fullscreen.set(currentPhoto)}
|
||||||
>
|
>
|
||||||
<source media="(min-width: 968px)" srcset={getThumbnail(photo.image.private_hash, 1400)}>
|
<source media="(min-width: 968px)" srcset={getThumbnail(photo.image.private_hash, 1400)}>
|
||||||
<source media="(min-width: 800px)" srcset={getThumbnail(photo.image.private_hash, 900)}>
|
<source media="(min-width: 800px)" srcset={getThumbnail(photo.image.private_hash, 900)}>
|
||||||
|
|||||||
82
src/organisms/Fullscreen.svelte
Normal file
82
src/organisms/Fullscreen.svelte
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<script>
|
||||||
|
import { fullscreen } from '../utils/store'
|
||||||
|
import { throttle, getThumbnail } from '../utils/functions'
|
||||||
|
|
||||||
|
// Dependencies
|
||||||
|
import imagesLoaded from 'imagesloaded'
|
||||||
|
|
||||||
|
// Components
|
||||||
|
import IconGlobe from '../atoms/IconGlobe'
|
||||||
|
import IconZoomOut from '../atoms/IconZoomOut'
|
||||||
|
|
||||||
|
// Variables
|
||||||
|
let scope
|
||||||
|
let loading = false
|
||||||
|
let open = false
|
||||||
|
let closed = false
|
||||||
|
|
||||||
|
// Wait for fullscreen store value
|
||||||
|
$: {
|
||||||
|
if ($fullscreen) openFullscreen($fullscreen)
|
||||||
|
else closeFullscreen()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Functions
|
||||||
|
*/
|
||||||
|
// Open fullscreen
|
||||||
|
const openFullscreen = throttle(currentPhoto => {
|
||||||
|
loading = true
|
||||||
|
|
||||||
|
if (!open) {
|
||||||
|
const img = document.createElement('img')
|
||||||
|
const imgContainer = scope.querySelector('.fullscreen__image')
|
||||||
|
img.src = getThumbnail(currentPhoto.image.private_hash, null, 1600, 'crop', 80)
|
||||||
|
img.alt = `${currentPhoto.name}, ${currentPhoto.location.name}, ${currentPhoto.location.country.name}`
|
||||||
|
img.width = 2400
|
||||||
|
img.height = 1600
|
||||||
|
imgContainer.append(img)
|
||||||
|
|
||||||
|
// Show fullscreen when new image is loaded
|
||||||
|
imagesLoaded(scope, instance => {
|
||||||
|
open = true
|
||||||
|
loading = false
|
||||||
|
closed = false
|
||||||
|
|
||||||
|
// Scroll to photo's center
|
||||||
|
imgContainer.scrollTo((img.clientWidth - scope.clientWidth) / 2, 0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, 800)
|
||||||
|
|
||||||
|
// Close fullscreen
|
||||||
|
const closeFullscreen = throttle(() => {
|
||||||
|
// Reset values
|
||||||
|
open = false
|
||||||
|
closed = true
|
||||||
|
|
||||||
|
// Clear image and reset fullscreen store value
|
||||||
|
setTimeout(() => {
|
||||||
|
scope.querySelector('.fullscreen__image').innerHTML = ''
|
||||||
|
fullscreen.set()
|
||||||
|
}, 800) // Transition duration
|
||||||
|
}, 800)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="fullscreen" bind:this={scope}>
|
||||||
|
<div class="fullscreen__image" class:is-open={open} on:click={closeFullscreen} />
|
||||||
|
|
||||||
|
<div class="fullscreen__loading" class:is-hidden={!loading}>
|
||||||
|
<IconGlobe width="24" color="#fff" animated="true" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="fullscreen__close" class:is-visible={open}>
|
||||||
|
<button class="button-control button-control--gray button-control--shadow dir-top" aria-label="Close"
|
||||||
|
on:click={closeFullscreen}
|
||||||
|
>
|
||||||
|
<IconZoomOut color="#fff" width="22" class="icon" />
|
||||||
|
<IconZoomOut color="#fff" width="22" class="icon" hidden="true" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -71,9 +71,11 @@
|
|||||||
countries,
|
countries,
|
||||||
locations
|
locations
|
||||||
} from '../utils/store'
|
} from '../utils/store'
|
||||||
|
import { stores } from '@sapper/app'
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import Transition from '../utils/Transition'
|
import Transition from '../utils/Transition'
|
||||||
|
import AnalyticsTracker from '../utils/AnalyticsTracker'
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
export const segment = null
|
export const segment = null
|
||||||
@@ -96,7 +98,6 @@
|
|||||||
$locations.forEach(loc => loc.country = $countries.find(cont => cont.id === loc.country.id))
|
$locations.forEach(loc => loc.country = $countries.find(cont => cont.id === loc.country.id))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<style lang="scss" global>
|
<style lang="scss" global>
|
||||||
@import "../style/style.scss";
|
@import "../style/style.scss";
|
||||||
</style>
|
</style>
|
||||||
@@ -104,3 +105,5 @@
|
|||||||
<slot></slot>
|
<slot></slot>
|
||||||
|
|
||||||
<Transition />
|
<Transition />
|
||||||
|
<AnalyticsTracker {stores} id={process.env.CONFIG.GA_TRACKER_ID} />
|
||||||
|
|
||||||
|
|||||||
@@ -6,21 +6,26 @@
|
|||||||
let preloaded
|
let preloaded
|
||||||
currentPhotos.subscribe(store => preloaded = store ? store : undefined)
|
currentPhotos.subscribe(store => preloaded = store ? store : undefined)
|
||||||
|
|
||||||
|
|
||||||
// Preload data
|
// Preload data
|
||||||
export async function preload (page, session) {
|
export async function preload (page, session) {
|
||||||
// Load the photos if not loaded
|
// Load the photos if not loaded
|
||||||
if (!preloaded) {
|
if (!preloaded) {
|
||||||
const req = await this.fetch(`${apiEndpoints.rest}/items/photos?fields=id,name,slug,date,image.*,location.*,location.country.*,created_on,modified_on&filter[location.slug][rlike]=%${page.params.location}%`)
|
// Fields
|
||||||
|
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()}&filter[location.slug][rlike]=%${page.params.place}%`)
|
||||||
const photos = await req.json()
|
const photos = await req.json()
|
||||||
return {
|
if (req.ok) {
|
||||||
photos: photos.data
|
return { photos: photos.data }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Use the store otherwise
|
// Use the store otherwise
|
||||||
else return {
|
else return {
|
||||||
photos: preloaded
|
photos: preloaded
|
||||||
}
|
}
|
||||||
|
this.error(404, 'Not found')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -34,33 +39,33 @@
|
|||||||
pageReady,
|
pageReady,
|
||||||
pageTransition
|
pageTransition
|
||||||
} from '../../../../utils/store'
|
} from '../../../../utils/store'
|
||||||
import { getThumbnail } from '../../../../utils/functions'
|
import { getThumbnail, analyticsUpdate } from '../../../../utils/functions'
|
||||||
const { page } = stores()
|
|
||||||
const dispatch = createEventDispatcher()
|
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import IconGlobe from '../../../../atoms/IconGlobe'
|
import IconGlobe from '../../../../atoms/IconGlobe'
|
||||||
import IconCross from '../../../../atoms/IconCross'
|
import IconCross from '../../../../atoms/IconCross'
|
||||||
import Carousel from '../../../../organisms/Carousel'
|
import Carousel from '../../../../organisms/Carousel'
|
||||||
|
import Fullscreen from '../../../../organisms/Fullscreen'
|
||||||
import SocialMetas from '../../../../utils/SocialMetas'
|
import SocialMetas from '../../../../utils/SocialMetas'
|
||||||
|
|
||||||
// Animations
|
// Animations
|
||||||
import { animateIn } from '../../../../animations/viewer'
|
import { animateIn } from '../../../../animations/viewer'
|
||||||
pageTransition.onAnimationEnd = animateIn
|
pageTransition.onAnimationEnd = animateIn
|
||||||
|
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
export let photos
|
export let photos
|
||||||
|
|
||||||
// Variables
|
// Variables
|
||||||
|
const { page } = stores()
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
let windowWidth
|
let windowWidth
|
||||||
let currentPhoto = photos.find(photo => photo.slug === $page.params.photo)
|
let currentPhoto = photos.find(photo => photo.slug === $page.params.photo)
|
||||||
|
|
||||||
// Update store current location
|
// Update store current location
|
||||||
if (!$currentLocation) currentLocation.set($locations.find(loc => loc.slug === $page.params.location))
|
if (!$currentLocation) currentLocation.set($locations.find(loc => loc.slug === $page.params.place))
|
||||||
if (!$currentPhotos) currentPhotos.set(photos)
|
if (!$currentPhotos) currentPhotos.set(photos)
|
||||||
|
|
||||||
// The photo has changed from the carousel
|
// Photo has changed from the Carousel component
|
||||||
const photoChanged = event => {
|
const photoChanged = event => {
|
||||||
currentPhoto = event.detail.currentPhoto
|
currentPhoto = event.detail.currentPhoto
|
||||||
|
|
||||||
@@ -69,6 +74,7 @@
|
|||||||
const windowPathname = window.location.pathname
|
const windowPathname = window.location.pathname
|
||||||
const newUrl = windowPathname.substring(0, windowPathname.lastIndexOf('/') + 1) + currentPhoto.slug
|
const newUrl = windowPathname.substring(0, windowPathname.lastIndexOf('/') + 1) + currentPhoto.slug
|
||||||
history.pushState('', document.title, newUrl)
|
history.pushState('', document.title, newUrl)
|
||||||
|
analyticsUpdate(newUrl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,11 +83,7 @@
|
|||||||
// On init, send an event to the Carousel component with the photoSlug to set currentIndex and then change the photo
|
// On init, send an event to the Carousel component with the photoSlug to set currentIndex and then change the photo
|
||||||
|
|
||||||
// Pop event from browser (prev/next)
|
// Pop event from browser (prev/next)
|
||||||
const changedUrl = event => {
|
const changedUrl = event => dispatch('changedUrl', { currentPhoto: currentPhoto })
|
||||||
dispatch('changedUrl', {
|
|
||||||
currentPhoto: currentPhoto
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -91,25 +93,21 @@
|
|||||||
// Page is loaded
|
// Page is loaded
|
||||||
pageReady.set(true)
|
pageReady.set(true)
|
||||||
|
|
||||||
/*
|
dispatch('changeUrl', { page: $page })
|
||||||
!!! TODO:
|
|
||||||
- Change the title with the current photo name and update Metas (with window location url)
|
|
||||||
*/
|
|
||||||
|
|
||||||
dispatch('changeUrl', {
|
|
||||||
page: $page
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
<!-- TODO:
|
||||||
|
- Change the title with the current photo name and update Metas (with window location url)
|
||||||
|
-->
|
||||||
<title>{$site.seo_name} – Photos of {$currentLocation.name}, {$currentLocation.country.name}</title>
|
<title>{$site.seo_name} – Photos of {$currentLocation.name}, {$currentLocation.country.name}</title>
|
||||||
<meta name="description" content="{$site.seo_name} {$currentLocation.name} {$currentLocation.description}">
|
<meta name="description" content="{$site.seo_name} {$currentLocation.name} {$currentLocation.description}">
|
||||||
<SocialMetas
|
<SocialMetas
|
||||||
|
url="https://{$page.host}/viewer/{currentPhoto.location.country.slug}/{currentPhoto.location.slug}/{currentPhoto.slug}"
|
||||||
title="{$site.seo_name} - Beautiful homes of {$currentLocation.name}, {$currentLocation.country.name}"
|
title="{$site.seo_name} - Beautiful homes of {$currentLocation.name}, {$currentLocation.country.name}"
|
||||||
description="{$site.seo_name} {$currentLocation.name} {$currentLocation.description}"
|
description="{$site.seo_name} {$currentLocation.name} {$currentLocation.description}"
|
||||||
image={getThumbnail(currentPhoto.image.private_hash, 1200, 630)}
|
image={getThumbnail(currentPhoto.image.private_hash, 1200, 630)}
|
||||||
url="//{$page.host.split(':3000')[0]}/viewer/{currentPhoto.location.country.slug}/{currentPhoto.location.slug}/{currentPhoto.slug}"
|
|
||||||
/>
|
/>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
@@ -119,14 +117,14 @@
|
|||||||
<div class="viewer__top">
|
<div class="viewer__top">
|
||||||
<p class="tip">Tap for fullscreen</p>
|
<p class="tip">Tap for fullscreen</p>
|
||||||
|
|
||||||
<div class="buttons">
|
<div class="viewer__buttons">
|
||||||
<a href="/choose" class="button-control button-control--dashed" aria-label="Change the location" rel="prefetch">
|
<a href="/choose" class="button-control button-control--dashed" aria-label="Change the location" rel="prefetch">
|
||||||
<IconGlobe color="#fff" width={windowWidth >= 768 ? 22 : 18} />
|
<IconGlobe color="#fff" width={windowWidth >= 768 ? 22 : 18} />
|
||||||
<svg>
|
<svg>
|
||||||
<circle cx="50%" cy="50%" r="{windowWidth >= 768 ? 32 : 24}px"></circle>
|
<circle cx="50%" cy="50%" r="{windowWidth >= 768 ? 32 : 24}px"></circle>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<a href="/location/{$currentLocation.country.slug}/{$currentLocation.slug}" class="button-control button-control--pink dir-bottom" aria-label="Close">
|
<a href="/location/{$currentLocation.country.slug}/{$currentLocation.slug}" class="button-control button-control--pink dir-bottom" aria-label="Back to photos" rel="prefetch">
|
||||||
<IconCross color="#fff" width="18" class="icon" />
|
<IconCross color="#fff" width="18" class="icon" />
|
||||||
<IconCross color="#fff" width="18" class="icon" hidden="true" />
|
<IconCross color="#fff" width="18" class="icon" hidden="true" />
|
||||||
</a>
|
</a>
|
||||||
@@ -134,9 +132,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Carousel
|
<Carousel
|
||||||
|
viewer="true"
|
||||||
photos={photos}
|
photos={photos}
|
||||||
viewer={true}
|
|
||||||
init={$page}
|
init={$page}
|
||||||
on:photoChange={photoChanged}
|
on:photoChange={photoChanged}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Fullscreen />
|
||||||
</section>
|
</section>
|
||||||
@@ -68,6 +68,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Effects
|
||||||
|
*/
|
||||||
|
&--shadow {
|
||||||
|
box-shadow: 0 0 10px rgba(#000, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** Directions
|
** Directions
|
||||||
*/
|
*/
|
||||||
|
|||||||
83
src/style/organisms/_fullscreen.scss
Normal file
83
src/style/organisms/_fullscreen.scss
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
.fullscreen {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 200;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
// Photo
|
||||||
|
&__image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(1.1);
|
||||||
|
transition: transform 0.8s $ease-quart, opacity 0.8s $ease-quart;
|
||||||
|
will-change: transform, opacity;
|
||||||
|
|
||||||
|
img {
|
||||||
|
position: relative;
|
||||||
|
z-index: 200;
|
||||||
|
display: block;
|
||||||
|
height: 100%;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open
|
||||||
|
&.is-open {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Controls
|
||||||
|
&__close {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 201;
|
||||||
|
bottom: 24px;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(1.1) translateY(24px);
|
||||||
|
transition: transform 0.8s $ease-quart, opacity 0.8s $ease-quart;
|
||||||
|
will-change: transform, opacity;
|
||||||
|
|
||||||
|
// Visible state
|
||||||
|
&.is-visible {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1) translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loading
|
||||||
|
&__loading {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 202;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
opacity: 1;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
transform-origin: 50% 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
background-color: $color-primary;
|
||||||
|
border-radius: 100%;
|
||||||
|
transition: transform 0.8s $ease-quart, opacity 0.8s $ease-quart;
|
||||||
|
will-change: transform, opacity;
|
||||||
|
|
||||||
|
// Hidden state
|
||||||
|
&.is-hidden {
|
||||||
|
transform: scale(1.05) translate(-50%, -50%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,9 +44,10 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Buttons
|
// Buttons
|
||||||
.buttons {
|
&__buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
@@ -57,7 +58,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -86,12 +86,10 @@
|
|||||||
right: 0;
|
right: 0;
|
||||||
|
|
||||||
// Specific box shadow for images
|
// Specific box shadow for images
|
||||||
&__images {
|
&__photo {
|
||||||
&--photo {
|
|
||||||
box-shadow: 0 pxVW(16px) pxVW(40) rgba(#2E025C, 0.4);
|
box-shadow: 0 pxVW(16px) pxVW(40) rgba(#2E025C, 0.4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Informations
|
// Informations
|
||||||
&__infos {
|
&__infos {
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
// Organisms
|
// Organisms
|
||||||
@import "organisms/carousel";
|
@import "organisms/carousel";
|
||||||
@import "organisms/photos";
|
@import "organisms/photos";
|
||||||
|
@import "organisms/fullscreen";
|
||||||
@import "organisms/locations";
|
@import "organisms/locations";
|
||||||
@import "organisms/pagination";
|
@import "organisms/pagination";
|
||||||
@import "organisms/footer";
|
@import "organisms/footer";
|
||||||
|
|||||||
27
src/utils/AnalyticsTracker.svelte
Normal file
27
src/utils/AnalyticsTracker.svelte
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<script>
|
||||||
|
import { analyticsUpdate } from '../utils/functions'
|
||||||
|
|
||||||
|
// Props
|
||||||
|
export let stores
|
||||||
|
export let id
|
||||||
|
|
||||||
|
// Variables
|
||||||
|
const { page } = stores()
|
||||||
|
|
||||||
|
// Init Google Analytics
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.dataLayer = window.dataLayer || []
|
||||||
|
window.gtag = function gtag() {
|
||||||
|
window.dataLayer.push(arguments)
|
||||||
|
}
|
||||||
|
window.gtag('js', new Date())
|
||||||
|
window.gtag('config', id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push new page to GA
|
||||||
|
$: analyticsUpdate($page.path, id)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<script async src="https://www.googletagmanager.com/gtag/js?id={id}"></script>
|
||||||
|
</svelte:head>
|
||||||
@@ -175,3 +175,15 @@ export const parallaxAnime = (element, anime) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Google Analytics send page
|
||||||
|
*/
|
||||||
|
export const analyticsUpdate = (page, id = process.env.CONFIG.GA_TRACKER_ID) => {
|
||||||
|
if (typeof gtag !== 'undefined') {
|
||||||
|
window.gtag('config', id, {
|
||||||
|
page_path: page
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,10 +8,9 @@ export const dev = process.env.NODE_ENV === 'development'
|
|||||||
/* ==========================================================================
|
/* ==========================================================================
|
||||||
Site related
|
Site related
|
||||||
========================================================================== */
|
========================================================================== */
|
||||||
const apiEndpoint = dev ? 'http://api.housesof.localhost/how' : 'https://api.housesof.world/_'
|
const apiEndpoint = dev ? process.env.CONFIG.API_URL_DEV : process.env.CONFIG.API_URL_PROD
|
||||||
const apiAccessToken = 'NJk0urljsdSvApUDzWxGgoO6'
|
|
||||||
export const apiEndpoints = {
|
export const apiEndpoints = {
|
||||||
gql: `${apiEndpoint}/gql?access_token=${apiAccessToken}`,
|
gql: `${apiEndpoint}/gql?access_token=${process.env.CONFIG.API_TOKEN}`,
|
||||||
rest: apiEndpoint
|
rest: apiEndpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,6 +29,7 @@ export let pageReady = writable(false)
|
|||||||
export const pageTransition = {
|
export const pageTransition = {
|
||||||
onAnimationEnd () {}
|
onAnimationEnd () {}
|
||||||
}
|
}
|
||||||
|
export let fullscreen = writable()
|
||||||
|
|
||||||
|
|
||||||
/* ==========================================================================
|
/* ==========================================================================
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 860 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 270 KiB |
Reference in New Issue
Block a user