diff --git a/.env b/.env new file mode 100644 index 0000000..6d2fd99 --- /dev/null +++ b/.env @@ -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 diff --git a/package.json b/package.json index c4c93a2..0d01fab 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "@rollup/plugin-replace": "^2.3.1", "autoprefixer": "^9.7.5", "babel-plugin-module-resolver": "^4.0.0", + "dotenv": "^8.2.0", "eslint-config-standard": "^14.1.1", "eslint-plugin-import": "^2.20.1", "eslint-plugin-node": "^11.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3b6208a..12e497c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,6 +19,7 @@ devDependencies: '@rollup/plugin-replace': 2.3.1_rollup@2.2.0 autoprefixer: 9.7.5 babel-plugin-module-resolver: 4.0.0 + dotenv: 8.2.0 eslint-config-standard: 14.1.1_13a54f81caffeb9134dc06c172bdde71 eslint-plugin-import: 2.20.1 eslint-plugin-node: 11.0.0 @@ -1649,6 +1650,12 @@ packages: node: '>=6.0.0' resolution: 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: dependencies: jsbn: 0.1.1 @@ -4574,6 +4581,7 @@ specifiers: autoprefixer: ^9.7.5 babel-plugin-module-resolver: ^4.0.0 compression: ^1.7.4 + dotenv: ^8.2.0 eslint-config-standard: ^14.1.1 eslint-plugin-import: ^2.20.1 eslint-plugin-node: ^11.0.0 diff --git a/rollup.config.js b/rollup.config.js index 431b899..3d21ca6 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -7,121 +7,121 @@ import babel from 'rollup-plugin-babel' // import browsersync from 'rollup-plugin-browsersync' import autoPreprocess from 'svelte-preprocess' 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' // Define environment and things const mode = process.env.NODE_ENV const dev = mode === 'development' const legacy = !!process.env.SAPPER_LEGACY_BUILD +const replaceOptions = { + 'process.env.NODE_ENV': JSON.stringify(mode), + 'process.env.CONFIG': JSON.stringify(config().parsed) +} // Svelte const onwarn = (warning, onwarn) => (warning.code === 'CIRCULAR_DEPENDENCY' && /[/\\]@sapper[/\\]/.test(warning.message)) || onwarn(warning) // Preprocessors const preprocess = autoPreprocess({ - scss: { - includePaths: ['src', 'node_modules'], - renderSync: true - }, - postcss: true + scss: { + includePaths: ['src', 'node_modules'], + renderSync: true + }, + postcss: true }) export default { - /* - ** Client - */ - client: { - input: config.client.input(), - output: { - ...config.client.output(), - // ...dev && { exports: 'named' } - }, - // experimentalCodeSplitting: true, - plugins: [ - // Javascript - replace({ - 'process.browser': true, - 'process.env.NODE_ENV': JSON.stringify(mode) - }), - svelte({ - dev, - hydratable: true, - emitCss: true, - // css: css => css.write('static/bundle.css'), - preprocess - }), - resolve({ - browser: true, - extensions: ['.mjs', '.js', '.svelte', '.scss', '.json', '.html'], - dedupe: ['svelte'] - }), - commonjs(), - // dev && eslint(), - legacy && babel({ - extensions: ['.js', '.mjs', '.html', '.svelte'], - exclude: ['*.scss', '*.css', 'node_modules/@babel/**'], - runtimeHelpers: true - }), + /* + ** Client + */ + client: { + input: sapperConfig.client.input(), + output: sapperConfig.client.output(), + plugins: [ + // Javascript + replace({ + 'process.browser': true, + ...replaceOptions + }), + svelte({ + dev, + preprocess, + hydratable: true, + emitCss: true, + // css: css => css.write('static/bundle.css') + }), + resolve({ + browser: true, + extensions: ['.mjs', '.js', '.svelte', '.scss', '.json', '.html'], + dedupe: ['svelte'] + }), + commonjs(), + // dev && eslint(), + legacy && babel({ + extensions: ['.js', '.mjs', '.html', '.svelte'], + exclude: ['*.scss', '*.css', 'node_modules/@babel/**'], + runtimeHelpers: true + }), - // Compress Javascript - !dev && terser({ - module: true - }), - ], + // Compress Javascript + !dev && terser({ + module: true + }), + ], - onwarn, - }, + onwarn, + }, - /* - ** Server - */ - server: { - input: config.server.input(), - output: config.server.output(), - plugins: [ - replace({ - 'process.browser': false, - 'process.env.NODE_ENV': JSON.stringify(mode) - }), - svelte({ - dev, - generate: 'ssr', - preprocess - }), - resolve({ - browser: true, - extensions: ['.mjs', '.js', '.json', '.html', '.svelte', '.scss'], - dedupe: ['svelte'] - }), - commonjs(), - ], - external: Object.keys(pkg.dependencies).concat( - require('module').builtinModules || Object.keys(process.binding('natives')) - ), - - onwarn, - }, + /* + ** Server + */ + server: { + input: sapperConfig.server.input(), + output: sapperConfig.server.output(), + plugins: [ + replace({ + 'process.browser': false, + ...replaceOptions + }), + svelte({ + dev, + preprocess, + generate: 'ssr' + }), + resolve({ + browser: true, + extensions: ['.mjs', '.js', '.json', '.html', '.svelte', '.scss'], + dedupe: ['svelte'] + }), + commonjs(), + ], + external: Object.keys(pkg.dependencies).concat( + require('module').builtinModules || Object.keys(process.binding('natives')) + ), + onwarn, + }, - /* - ** Service worker - */ - // serviceworker: { - // input: config.serviceworker.input(), - // output: config.serviceworker.output(), - // plugins: [ - // resolve(), - // replace({ - // 'process.browser': true, - // 'process.env.NODE_ENV': JSON.stringify(mode) - // }), - // commonjs(), - // !dev && terser() - // ], + /* + ** Service worker + */ + // serviceworker: { + // input: sapperConfig.serviceworker.input(), + // output: sapperConfig.serviceworker.output(), + // plugins: [ + // resolve(), + // replace({ + // 'process.browser': true, + // ...replaceOptions + // }), + // commonjs(), + // !dev && terser() + // ], - // onwarn, - // } + // onwarn, + // } } diff --git a/src/atoms/IconZoomOut.svelte b/src/atoms/IconZoomOut.svelte new file mode 100644 index 0000000..a8dce0b --- /dev/null +++ b/src/atoms/IconZoomOut.svelte @@ -0,0 +1,9 @@ + + + + + diff --git a/src/organisms/Carousel.svelte b/src/organisms/Carousel.svelte index aa40d42..d2082fd 100644 --- a/src/organisms/Carousel.svelte +++ b/src/organisms/Carousel.svelte @@ -1,7 +1,7 @@ + +
+
+ +
+ +
+ +
+ +
+
diff --git a/src/routes/_layout.svelte b/src/routes/_layout.svelte index e519391..42b4b98 100644 --- a/src/routes/_layout.svelte +++ b/src/routes/_layout.svelte @@ -71,9 +71,11 @@ countries, locations } from '../utils/store' + import { stores } from '@sapper/app' // Components import Transition from '../utils/Transition' + import AnalyticsTracker from '../utils/AnalyticsTracker' // Props export const segment = null @@ -96,7 +98,6 @@ $locations.forEach(loc => loc.country = $countries.find(cont => cont.id === loc.country.id)) - @@ -104,3 +105,5 @@ + + diff --git a/src/routes/viewer/[country]/[location]/[photo].svelte b/src/routes/viewer/[country]/[place]/[photo].svelte similarity index 76% rename from src/routes/viewer/[country]/[location]/[photo].svelte rename to src/routes/viewer/[country]/[place]/[photo].svelte index 964d8a2..a860b19 100644 --- a/src/routes/viewer/[country]/[location]/[photo].svelte +++ b/src/routes/viewer/[country]/[place]/[photo].svelte @@ -6,21 +6,26 @@ 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 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() - return { - photos: photos.data + if (req.ok) { + return { photos: photos.data } } } // Use the store otherwise else return { photos: preloaded } + this.error(404, 'Not found') } @@ -34,33 +39,33 @@ pageReady, pageTransition } from '../../../../utils/store' - import { getThumbnail } from '../../../../utils/functions' - const { page } = stores() - const dispatch = createEventDispatcher() + import { getThumbnail, analyticsUpdate } 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 SocialMetas from '../../../../utils/SocialMetas' // Animations import { animateIn } from '../../../../animations/viewer' pageTransition.onAnimationEnd = animateIn - // Props export let photos // Variables + const { page } = stores() + const dispatch = createEventDispatcher() let windowWidth let currentPhoto = photos.find(photo => photo.slug === $page.params.photo) // 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) - // The photo has changed from the carousel + // Photo has changed from the Carousel component const photoChanged = event => { currentPhoto = event.detail.currentPhoto @@ -69,6 +74,7 @@ const windowPathname = window.location.pathname const newUrl = windowPathname.substring(0, windowPathname.lastIndexOf('/') + 1) + currentPhoto.slug 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 // Pop event from browser (prev/next) - const changedUrl = event => { - dispatch('changedUrl', { - currentPhoto: currentPhoto - }) - } + const changedUrl = event => dispatch('changedUrl', { currentPhoto: currentPhoto }) /* @@ -91,25 +93,21 @@ // Page is loaded pageReady.set(true) - /* - !!! TODO: - - Change the title with the current photo name and update Metas (with window location url) - */ - - dispatch('changeUrl', { - page: $page - }) + dispatch('changeUrl', { page: $page }) }) + {$site.seo_name} – Photos of {$currentLocation.name}, {$currentLocation.country.name} @@ -119,14 +117,14 @@

Tap for fullscreen

-
+
= 768 ? 22 : 18} /> - + @@ -134,9 +132,11 @@
+ + diff --git a/src/style/atoms/_button-control.scss b/src/style/atoms/_button-control.scss index c8e6344..544d0d3 100644 --- a/src/style/atoms/_button-control.scss +++ b/src/style/atoms/_button-control.scss @@ -68,6 +68,15 @@ } + /* + ** Effects + */ + &--shadow { + box-shadow: 0 0 10px rgba(#000, 0.4); + } + + + /* ** Directions */ diff --git a/src/style/organisms/_fullscreen.scss b/src/style/organisms/_fullscreen.scss new file mode 100644 index 0000000..857b3fd --- /dev/null +++ b/src/style/organisms/_fullscreen.scss @@ -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; + } + } +} \ No newline at end of file diff --git a/src/style/pages/_viewer.scss b/src/style/pages/_viewer.scss index a5b97f1..393250d 100644 --- a/src/style/pages/_viewer.scss +++ b/src/style/pages/_viewer.scss @@ -44,17 +44,17 @@ display: none; } } + } - // Buttons - .buttons { - display: flex; + // Buttons + &__buttons { + display: flex; - a { - margin-left: 16px; + a { + margin-left: 16px; - &:first-child { - margin-left: 0; - } + &:first-child { + margin-left: 0; } } } @@ -86,10 +86,8 @@ right: 0; // Specific box shadow for images - &__images { - &--photo { - box-shadow: 0 pxVW(16px) pxVW(40) rgba(#2E025C, 0.4); - } + &__photo { + box-shadow: 0 pxVW(16px) pxVW(40) rgba(#2E025C, 0.4); } } diff --git a/src/style/style.scss b/src/style/style.scss index bfd3b13..c3e3b8c 100644 --- a/src/style/style.scss +++ b/src/style/style.scss @@ -31,6 +31,7 @@ // Organisms @import "organisms/carousel"; @import "organisms/photos"; +@import "organisms/fullscreen"; @import "organisms/locations"; @import "organisms/pagination"; @import "organisms/footer"; diff --git a/src/utils/AnalyticsTracker.svelte b/src/utils/AnalyticsTracker.svelte new file mode 100644 index 0000000..7b73a31 --- /dev/null +++ b/src/utils/AnalyticsTracker.svelte @@ -0,0 +1,27 @@ + + + + + diff --git a/src/utils/functions.js b/src/utils/functions.js index cafbe64..a78f875 100644 --- a/src/utils/functions.js +++ b/src/utils/functions.js @@ -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 + }) + } +} diff --git a/src/utils/store.js b/src/utils/store.js index 0555684..b816f3c 100644 --- a/src/utils/store.js +++ b/src/utils/store.js @@ -8,10 +8,9 @@ export const dev = process.env.NODE_ENV === 'development' /* ========================================================================== Site related ========================================================================== */ -const apiEndpoint = dev ? 'http://api.housesof.localhost/how' : 'https://api.housesof.world/_' -const apiAccessToken = 'NJk0urljsdSvApUDzWxGgoO6' +const apiEndpoint = dev ? process.env.CONFIG.API_URL_DEV : process.env.CONFIG.API_URL_PROD export const apiEndpoints = { - gql: `${apiEndpoint}/gql?access_token=${apiAccessToken}`, + gql: `${apiEndpoint}/gql?access_token=${process.env.CONFIG.API_TOKEN}`, rest: apiEndpoint } @@ -30,6 +29,7 @@ export let pageReady = writable(false) export const pageTransition = { onAnimationEnd () {} } +export let fullscreen = writable() /* ========================================================================== diff --git a/static/img/GrayRoad15.jpg b/static/img/GrayRoad15.jpg deleted file mode 100644 index 164b6e9..0000000 Binary files a/static/img/GrayRoad15.jpg and /dev/null differ diff --git a/static/img/RyanStreet58.jpg b/static/img/RyanStreet58.jpg deleted file mode 100644 index 627990d..0000000 Binary files a/static/img/RyanStreet58.jpg and /dev/null differ diff --git a/static/img/TurinStreet33.jpg b/static/img/TurinStreet33.jpg deleted file mode 100644 index 0e7c35b..0000000 Binary files a/static/img/TurinStreet33.jpg and /dev/null differ diff --git a/static/img/globe-old.png b/static/img/globe-old.png deleted file mode 100644 index 76b8108..0000000 Binary files a/static/img/globe-old.png and /dev/null differ