First commit

Explore Svelte and Sapper
This commit is contained in:
2019-12-23 18:43:22 +01:00
commit c003b969d5
18 changed files with 2172 additions and 0 deletions

5
src/client.js Normal file
View File

@@ -0,0 +1,5 @@
import * as sapper from '@sapper/app'
sapper.start({
target: document.getElementById('site')
})

14
src/components/Nav.svelte Normal file
View File

@@ -0,0 +1,14 @@
<script>
export let segment;
</script>
<nav>
<ul>
<li><a class:selected='{segment === undefined}' href='.'>home</a></li>
<li><a class:selected='{segment === "about"}' href='about'>about</a></li>
<!-- for the blog link, we're using rel=prefetch so that Sapper prefetches
the blog data when we hover over the link or tap it on a touchscreen -->
<li><a rel=prefetch class:selected='{segment === "blog"}' href='blog'>blog</a></li>
</ul>
</nav>

View File

@@ -0,0 +1,36 @@
<script>
import { slide } from 'svelte/transition'
import dayjs from 'dayjs'
import advancedFormat from 'dayjs/plugin/advancedFormat'
import * as fn from '../functions'
export let photo
export let index
export let location
// Manipulate data
const thumbnail = photo.image.data.thumbnails.find(t => t.url.includes('key=large'))
index = (index < 10 ? '0' : null) + index
// Format the date
dayjs.extend(advancedFormat)
const date = dayjs(photo.created_on).format('MMM Do, YYYY')
</script>
<div class="card" in:slide out:slide>
<div class="card-image">
<figure class="image is-4by3">
<img src="{thumbnail.url}" alt="{photo.name}, {location.name}, {location.country.name}" width="{thumbnail.width}" height="{thumbnail.height}">
</figure>
</div>
<div class="card-content">
<h4 class="title is-4">{photo.name}</h4>
<p>{location.name}, {location.country.name}</p>
</div>
<footer class="card-footer">
<span class="card-footer-item">{date}</span>
<span class="card-footer-item">#{index}</span>
</footer>
</div>

38
src/functions.js Normal file
View File

@@ -0,0 +1,38 @@
/*
** Get API function
*/
export const api = async (query) => {
const res = await fetch('http://api.housesof.localhost/how/gql?access_token=NJk0urljsdSvApUDzWxGgoO6', {
method: 'post',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: `{${query}}` })
})
const data = await res.json()
return data.data
}
/*
** Load API function
*/
export const loadAPI = async (requests = []) => {
// Fetch all requests
return await Promise.all(requests.map(req => api(req)
.then(res => res.json())
.then(data => data.data)
))
}
/*
** Slufigy a string
*/
export const slug = string => {
return string.toString().toLowerCase().trim()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.replace(/\s+/g, '-')
.replace(/&/g, '-and-')
.replace(/[^\w\-]+/g, '')
.replace(/\-\-+/g, '-')
}

21
src/parts/Footer.svelte Normal file
View File

@@ -0,0 +1,21 @@
<script>
import { site } from '../store'
</script>
<!-- <pre>{JSON.stringify($site, '', 2)}</pre> -->
<footer class="footer">
<div class="left">
</div>
<div class="right">
<div class="instagram">
<a href="https://instagram.com/cetrucflotte" target="_blank">Instagram</a>
</div>
<div class="ctf">
<a href="https://cetrucflotte.com" target="_blank">A project by <img src="/assets/img/logo-cetrucflotte.png" alt="Cetrucflotte" width="118" height="22"></a>
</div>
</div>
</footer>

40
src/routes/_error.svelte Normal file
View File

@@ -0,0 +1,40 @@
<script>
export let status;
export let error;
const dev = process.env.NODE_ENV === 'development';
</script>
<style>
h1, p {
margin: 0 auto;
}
h1 {
font-size: 2.8em;
font-weight: 700;
margin: 0 0 0.5em 0;
}
p {
margin: 1em auto;
}
@media (min-width: 480px) {
h1 {
font-size: 4em;
}
}
</style>
<svelte:head>
<title>{status}</title>
</svelte:head>
<h1>{status}</h1>
<p>{error.message}</p>
{#if dev && error.stack}
<pre>{error.stack}</pre>
{/if}

86
src/routes/_layout.svelte Normal file
View File

@@ -0,0 +1,86 @@
<script context="module">
import { locations, countries, site } from '../store'
export async function preload (page, segment) {
const res = await this.fetch('http://api.housesof.localhost/how/gql?access_token=NJk0urljsdSvApUDzWxGgoO6', {
method: 'post',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: `{
site {
data {
description
explore_globe
explore_list
instagram
}
}
countries {
data {
name
flag { metadata }
}
}
locations (filter: { status_eq: "published" }) {
data {
name
region
country { name }
}
}
}`})
})
const data = await res.json()
countries.set(data.data.countries.data)
locations.set(data.data.locations.data)
site.set(data.data.site.data[0])
}
</script>
<script>
import '../../node_modules/bulma/css/bulma.min.css'
import * as fn from '../functions'
// import SwitcherLarge from '../components/Photos.svelte'
</script>
<nav class="navbar is-danger" role="navigation" aria-label="main navigation">
<div class="container">
<div class="navbar-start">
<div class="navbar-brand">
<a class="navbar-item" href="/">
<strong>Houses Of</strong>
</a>
</div>
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link" href="/">Locations</a>
<div class="navbar-dropdown">
{#each $locations as location}
<a class="navbar-item" href="/location/{fn.slug(location.name)}">{location.name}</a>
{/each}
</div>
</div>
</div>
</div>
</nav>
<slot></slot>
<footer class="footer">
<div class="container">
<div class="content columns">
<div class="column is-7">
<p><strong>Houses Of</strong></p>
</div>
<div class="column columns">
<p class="column">
<a href="https://instagram.com/{$site.instagram}" target="_blank">Instagram</a>
</p>
<p class="column">
<a href="https://cetrucflotte.com" target="_blank">A project by Cetrucflotte</a>
</p>
</div>
</div>
</div>
</footer>

7
src/routes/about.svelte Normal file
View File

@@ -0,0 +1,7 @@
<svelte:head>
<title>About</title>
</svelte:head>
<h1>About this site</h1>
<p>This is the 'about' page. There's not much here.</p>

29
src/routes/index.svelte Normal file
View File

@@ -0,0 +1,29 @@
<script>
import { slide } from 'svelte/transition'
import { locations, countries, site } from '../store'
import * as fn from '../functions'
</script>
<svelte:head>
<title>Houses Of - Beautiful houses of planet Earth</title>
</svelte:head>
<div class="container" in:slide out:slide>
<section class="section">
<h1 class="title is-2">Houses Of</h1>
<article class="message is-dark">
<div class="message-body">{$site.description}</div>
</article>
</section>
<section class="section">
<h3 class="title is-3">Locations</h3>
<ul>
{#each $locations as location}
<li>
<a href="/location/{fn.slug(location.name)}">{location.name}</a>
</li>
{/each}
</ul>
</section>
</div>

View File

@@ -0,0 +1,54 @@
<script context="module">
export async function preload (page, session) {
const location = await this.fetch('//api.housesof.localhost/how/gql?access_token=NJk0urljsdSvApUDzWxGgoO6', {
method: 'post',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: `{
locations (filter: { name_eq: "${page.params.location}" }) {
data {
name
region
country { name }
description
}
}
}`})
})
const photos = await this.fetch(`http://api.housesof.localhost/how/items/photos?fields=id,name,image.data,created_on&filter[location.name][rlike]=%${page.params.location}%`)
const locationData = await location.json()
const photosData = await photos.json()
return {
location: locationData.data.locations.data[0],
photos: photosData.data
}
}
</script>
<script>
import { slide } from 'svelte/transition'
import Photo from '../../components/Photo.svelte'
export let location
export let photos
</script>
<svelte:head>
<title>Houses Of - Beautiful houses of {location.name}, {location.country.name}</title>
</svelte:head>
<div class="section container" in:slide out:slide>
<div class="content">
<h1 class="title is-2">{location.name}, {location.country.name}</h1>
{#if location.description}
<p>Houses Of {location.name} {location.description}</p>
{/if}
</div>
<div class="columns">
{#each photos as photo, index (photo.id)}
<div class="column">
<Photo {photo} {location} index={index + 1} />
</div>
{/each}
</div>
</div>

17
src/server.js Normal file
View File

@@ -0,0 +1,17 @@
import sirv from 'sirv'
import polka from 'polka'
import compression from 'compression'
import * as sapper from '@sapper/server'
const { PORT, NODE_ENV } = process.env
const dev = NODE_ENV === 'development'
polka() // You can also use Express
.use(
compression({ threshold: 0 }),
sirv('static', { dev }),
sapper.middleware()
)
.listen(PORT, err => {
if (err) console.log('error', err)
})

82
src/service-worker.js Normal file
View File

@@ -0,0 +1,82 @@
import { timestamp, files, shell, routes } from '@sapper/service-worker'
const ASSETS = `cache${timestamp}`
// `shell` is an array of all the files generated by the bundler,
// `files` is an array of everything in the `static` directory
const to_cache = shell.concat(files)
const cached = new Set(to_cache)
self.addEventListener('install', event => {
event.waitUntil(
caches
.open(ASSETS)
.then(cache => cache.addAll(to_cache))
.then(() => {
self.skipWaiting()
})
)
})
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(async keys => {
// delete old caches
for (const key of keys) {
if (key !== ASSETS) await caches.delete(key)
}
self.clients.claim()
})
)
})
self.addEventListener('fetch', event => {
if (event.request.method !== 'GET' || event.request.headers.has('range')) return
const url = new URL(event.request.url)
// don't try to handle e.g. data: URIs
if (!url.protocol.startsWith('http')) return
// ignore dev server requests
if (url.hostname === self.location.hostname && url.port !== self.location.port) return
// always serve static files and bundler-generated assets from cache
if (url.host === self.location.host && cached.has(url.pathname)) {
event.respondWith(caches.match(event.request))
return
}
// for pages, you might want to serve a shell `service-worker-index.html` file,
// which Sapper has generated for you. It's not right for every
// app, but if it's right for yours then uncomment this section
/*
if (url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) {
event.respondWith(caches.match('/service-worker-index.html'))
return
}
*/
if (event.request.cache === 'only-if-cached') return
// for everything else, try the network first, falling back to
// cache if the user is offline. (If the pages never change, you
// might prefer a cache-first approach to a network-first one.)
event.respondWith(
caches
.open(`offline${timestamp}`)
.then(async cache => {
try {
const response = await fetch(event.request)
cache.put(event.request, response.clone())
return response
} catch(err) {
const response = await cache.match(event.request)
if (response) return response
throw err
}
})
)
})

15
src/store.js Normal file
View File

@@ -0,0 +1,15 @@
import * as fn from './functions'
import { writable } from 'svelte/store'
/* ==========================================================================
Site related
========================================================================== */
export let currentLocation = {}
export let loaded = writable(false)
// Data
export let locations = writable()
export let countries = writable()
export let site = writable()

17
src/template.html Normal file
View File

@@ -0,0 +1,17 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width,initial-scale=1">
%sapper.base%
%sapper.styles%
%sapper.head%
</head>
<body>
<div id="site">%sapper.html%</div>
%sapper.scripts%
</body>
</html>