mirror of
https://github.com/kremalicious/portfolio.git
synced 2024-12-23 01:29:41 +01:00
refactorings
This commit is contained in:
parent
0e78f34d15
commit
f5f6c0b5fd
@ -1,6 +1,6 @@
|
|||||||
import './src/styles/global.scss'
|
import './src/styles/global.scss'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import AppProvider from './src/store/provider'
|
import AppProvider from './src/store/Provider'
|
||||||
import wrapPageElementWithTransition from './src/helpers/wrapPageElement'
|
import wrapPageElementWithTransition from './src/helpers/wrapPageElement'
|
||||||
|
|
||||||
// IntersectionObserver polyfill for gatsby-image (Safari, IE)
|
// IntersectionObserver polyfill for gatsby-image (Safari, IE)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { renderToString } from 'react-dom/server'
|
import { renderToString } from 'react-dom/server'
|
||||||
import AppProvider from './src/store/provider'
|
import AppProvider from './src/store/Provider'
|
||||||
import wrapPageElementWithTransition from './src/helpers/wrapPageElement'
|
import wrapPageElementWithTransition from './src/helpers/wrapPageElement'
|
||||||
|
|
||||||
export const replaceRenderer = ({ bodyComponent, replaceBodyHTMLString }) => {
|
export const replaceRenderer = ({ bodyComponent, replaceBodyHTMLString }) => {
|
||||||
|
@ -2,3 +2,5 @@ import 'jest-dom/extend-expect'
|
|||||||
|
|
||||||
// this is basically: afterEach(cleanup)
|
// this is basically: afterEach(cleanup)
|
||||||
import 'react-testing-library/cleanup-after-each'
|
import 'react-testing-library/cleanup-after-each'
|
||||||
|
|
||||||
|
import 'jest-canvas-mock'
|
||||||
|
@ -71,6 +71,7 @@
|
|||||||
"eslint-plugin-react": "^7.12.4",
|
"eslint-plugin-react": "^7.12.4",
|
||||||
"identity-obj-proxy": "^3.0.0",
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"jest": "^24.7.1",
|
"jest": "^24.7.1",
|
||||||
|
"jest-canvas-mock": "^2.0.0-beta.1",
|
||||||
"jest-dom": "^3.1.3",
|
"jest-dom": "^3.1.3",
|
||||||
"ora": "^3.4.0",
|
"ora": "^3.4.0",
|
||||||
"prepend": "^1.0.2",
|
"prepend": "^1.0.2",
|
||||||
|
@ -41,7 +41,7 @@ export default class Vcard extends PureComponent {
|
|||||||
|
|
||||||
const handleAddressbookClick = e => {
|
const handleAddressbookClick = e => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
constructVcard(meta)
|
init(meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -60,6 +60,16 @@ export default class Vcard extends PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const init = async meta => {
|
||||||
|
const photoSrc = meta.avatar.childImageSharp.resize.src
|
||||||
|
|
||||||
|
// first, convert the avatar to base64, then construct all vCard elements
|
||||||
|
const dataUrl = await toDataURL(photoSrc, 'image/jpeg')
|
||||||
|
const vcard = await constructVcard(dataUrl, meta)
|
||||||
|
|
||||||
|
downloadVcard(vcard, meta)
|
||||||
|
}
|
||||||
|
|
||||||
// Construct the download from a blob of the just constructed vCard,
|
// Construct the download from a blob of the just constructed vCard,
|
||||||
// and save it to user's file system
|
// and save it to user's file system
|
||||||
export const downloadVcard = (vcard, meta) => {
|
export const downloadVcard = (vcard, meta) => {
|
||||||
@ -69,60 +79,54 @@ export const downloadVcard = (vcard, meta) => {
|
|||||||
saveAs(blob, name)
|
saveAs(blob, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const constructVcard = meta => {
|
export const constructVcard = async (dataUrl, meta) => {
|
||||||
const contact = new vCard()
|
const contact = new vCard()
|
||||||
const photoSrc = meta.avatar.childImageSharp.resize.src
|
|
||||||
|
|
||||||
// first, convert the avatar to base64, then construct all vCard elements
|
// stripping this data out of base64 string is required
|
||||||
toDataURL(
|
// for vcard to actually display the image for whatever reason
|
||||||
photoSrc,
|
// const dataUrlCleaned = dataUrl.split('data:image/jpeg;base64,').join('')
|
||||||
dataUrl => {
|
// contact.set('photo', dataUrlCleaned, { encoding: 'b', type: 'JPEG' })
|
||||||
// stripping this data out of base64 string is required
|
contact.set('fn', meta.title)
|
||||||
// for vcard to actually display the image for whatever reason
|
contact.set('title', meta.tagline)
|
||||||
const dataUrlCleaned = dataUrl.split('data:image/jpeg;base64,').join('')
|
contact.set('email', meta.email)
|
||||||
contact.set('photo', dataUrlCleaned, { encoding: 'b', type: 'JPEG' })
|
contact.set('url', meta.url, { type: 'Portfolio' })
|
||||||
contact.set('fn', meta.title)
|
contact.add('url', meta.social.Blog, { type: 'Blog' })
|
||||||
contact.set('title', meta.tagline)
|
contact.set('nickname', 'kremalicious')
|
||||||
contact.set('email', meta.email)
|
contact.add('x-socialprofile', meta.social.Twitter, { type: 'twitter' })
|
||||||
contact.set('url', meta.url, { type: 'Portfolio' })
|
contact.add('x-socialprofile', meta.social.GitHub, { type: 'GitHub' })
|
||||||
contact.add('url', meta.social.Blog, { type: 'Blog' })
|
|
||||||
contact.set('nickname', 'kremalicious')
|
|
||||||
contact.add('x-socialprofile', meta.social.Twitter, { type: 'twitter' })
|
|
||||||
contact.add('x-socialprofile', meta.social.GitHub, { type: 'GitHub' })
|
|
||||||
|
|
||||||
const vcard = contact.toString('3.0')
|
const vcard = contact.toString('3.0')
|
||||||
|
|
||||||
downloadVcard(vcard, meta)
|
return vcard
|
||||||
},
|
|
||||||
'image/jpeg'
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to create base64 string from avatar image
|
// Helper function to create base64 string from avatar image
|
||||||
// without the need to read image file from file system
|
// without the need to read image file from file system
|
||||||
export const toDataURL = (src, callback, outputFormat) => {
|
export const toDataURL = async (photoSrc, outputFormat) => {
|
||||||
const img = new Image()
|
const img = new Image()
|
||||||
img.crossOrigin = 'Anonymous'
|
img.crossOrigin = 'Anonymous'
|
||||||
|
img.src = photoSrc
|
||||||
|
|
||||||
img.onload = function() {
|
img.onload = () => {}
|
||||||
// yeah, we're gonna create a fake canvas to render the image
|
|
||||||
// and then create a base64 string from the rendered result
|
|
||||||
const canvas = document.createElement('canvas')
|
|
||||||
const ctx = canvas.getContext('2d')
|
|
||||||
let dataURL
|
|
||||||
|
|
||||||
canvas.height = this.naturalHeight
|
// yeah, we're gonna create a fake canvas to render the image
|
||||||
canvas.width = this.naturalWidth
|
// and then create a base64 string from the rendered result
|
||||||
ctx.drawImage(this, 0, 0)
|
const canvas = document.createElement('canvas')
|
||||||
dataURL = canvas.toDataURL(outputFormat)
|
const ctx = canvas.getContext('2d')
|
||||||
callback(dataURL)
|
let dataURL
|
||||||
}
|
|
||||||
|
|
||||||
img.src = src
|
canvas.height = img.naturalHeight
|
||||||
|
canvas.width = img.naturalWidth
|
||||||
|
ctx.drawImage(img, 0, 0)
|
||||||
|
dataURL = canvas.toDataURL(outputFormat)
|
||||||
|
|
||||||
if (img.complete || img.complete === undefined) {
|
// img.src = photoSrc
|
||||||
img.src =
|
|
||||||
'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=='
|
// if (img.complete || img.complete === undefined) {
|
||||||
img.src = src
|
// img.src =
|
||||||
}
|
// 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=='
|
||||||
|
// img.src = photoSrc
|
||||||
|
// }
|
||||||
|
|
||||||
|
return dataURL
|
||||||
}
|
}
|
||||||
|
@ -1,37 +1,36 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { render } from 'react-testing-library'
|
import { render } from 'react-testing-library'
|
||||||
import { StaticQuery } from 'gatsby'
|
import { StaticQuery } from 'gatsby'
|
||||||
import vCard from 'vcf'
|
import Vcard, { constructVcard, toDataURL, init } from './Vcard'
|
||||||
import Vcard, { constructVcard, downloadVcard, toDataURL } from './Vcard'
|
|
||||||
import data from '../../../jest/__fixtures__/meta.json'
|
import data from '../../../jest/__fixtures__/meta.json'
|
||||||
|
|
||||||
describe('Vcard', () => {
|
describe('Vcard', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
StaticQuery.mockImplementationOnce(({ render }) => render({ ...data }))
|
StaticQuery.mockImplementationOnce(({ render }) => render({ ...data }))
|
||||||
|
|
||||||
|
global.URL.createObjectURL = jest.fn()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders correctly', () => {
|
it('renders correctly', () => {
|
||||||
const { container } = render(<Vcard />)
|
const { container } = render(<Vcard />)
|
||||||
|
|
||||||
expect(container.firstChild).toBeInTheDocument()
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('vCard can be constructed', async () => {
|
it('combined vCard download process finishes', async () => {
|
||||||
await constructVcard(data.contentYaml)
|
await init(data.contentYaml)
|
||||||
})
|
|
||||||
|
|
||||||
it('vCard can be downloaded', async () => {
|
|
||||||
const contact = new vCard()
|
|
||||||
const vcard = contact.toString('3.0')
|
|
||||||
|
|
||||||
global.URL.createObjectURL = jest.fn(() => 'details')
|
|
||||||
await downloadVcard(vcard, data.contentYaml)
|
|
||||||
expect(global.URL.createObjectURL).toHaveBeenCalledTimes(1)
|
expect(global.URL.createObjectURL).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Base64 from image can be constructed', () => {
|
it('vCard can be constructed', async () => {
|
||||||
const photoSrc = data.contentYaml.avatar.childImageSharp.resize.src
|
const vcard = await constructVcard(
|
||||||
|
'data:image/jpeg;base64,00',
|
||||||
|
data.contentYaml
|
||||||
|
)
|
||||||
|
expect(vcard).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
toDataURL(photoSrc, () => null, 'image/jpeg')
|
it('Base64 from image can be constructed', async () => {
|
||||||
|
const dataUrl = await toDataURL('hello', 'image/jpeg')
|
||||||
|
expect(dataUrl).toBeDefined()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -6,9 +6,7 @@ import data from '../../../jest/__fixtures__/meta.json'
|
|||||||
|
|
||||||
describe('Availability', () => {
|
describe('Availability', () => {
|
||||||
it('renders correctly from data file values', () => {
|
it('renders correctly from data file values', () => {
|
||||||
useStaticQuery.mockImplementation(() => {
|
useStaticQuery.mockImplementation(() => ({ ...data }))
|
||||||
return { ...data }
|
|
||||||
})
|
|
||||||
const { container } = render(<Availability />)
|
const { container } = render(<Availability />)
|
||||||
expect(container.firstChild).toBeInTheDocument()
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
@ -5,9 +5,7 @@ import Networks from './Networks'
|
|||||||
import data from '../../../jest/__fixtures__/meta.json'
|
import data from '../../../jest/__fixtures__/meta.json'
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
useStaticQuery.mockImplementationOnce(() => {
|
useStaticQuery.mockImplementationOnce(() => ({ ...data }))
|
||||||
return { ...data }
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Networks', () => {
|
describe('Networks', () => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { render } from 'react-testing-library'
|
import { render } from 'react-testing-library'
|
||||||
import AppProvider from '../../store/provider'
|
import AppProvider from '../../store/Provider'
|
||||||
import ThemeSwitch from './ThemeSwitch'
|
import ThemeSwitch from './ThemeSwitch'
|
||||||
|
|
||||||
describe('ThemeSwitch', () => {
|
describe('ThemeSwitch', () => {
|
||||||
|
@ -11,9 +11,6 @@ const query = graphql`
|
|||||||
query {
|
query {
|
||||||
# the package.json file
|
# the package.json file
|
||||||
portfolioJson {
|
portfolioJson {
|
||||||
name
|
|
||||||
homepage
|
|
||||||
repository
|
|
||||||
bugs
|
bugs
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,7 +22,7 @@ const query = graphql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const FooterMarkup = ({ pkg, meta, year }) => {
|
const FooterMarkup = ({ pkg, meta, year }) => {
|
||||||
const classes = classNames('h-card', [styles.footer])
|
const classes = classNames('h-card', [styles.footer])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
22
src/components/organisms/Footer.test.jsx
Normal file
22
src/components/organisms/Footer.test.jsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render } from 'react-testing-library'
|
||||||
|
import { StaticQuery, useStaticQuery } from 'gatsby'
|
||||||
|
import Footer from './Footer'
|
||||||
|
import data from '../../../jest/__fixtures__/meta.json'
|
||||||
|
|
||||||
|
describe('Header', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
StaticQuery.mockImplementation(({ render }) =>
|
||||||
|
render({
|
||||||
|
...data,
|
||||||
|
portfolioJson: { bugs: '' }
|
||||||
|
})
|
||||||
|
)
|
||||||
|
useStaticQuery.mockImplementation(() => ({ ...data }))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders correctly', () => {
|
||||||
|
const { container } = render(<Footer />)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
22
src/components/organisms/Header.test.jsx
Normal file
22
src/components/organisms/Header.test.jsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render } from 'react-testing-library'
|
||||||
|
import { StaticQuery, useStaticQuery } from 'gatsby'
|
||||||
|
import Header from './Header'
|
||||||
|
import { Provider } from '../../store/createContext'
|
||||||
|
import data from '../../../jest/__fixtures__/meta.json'
|
||||||
|
|
||||||
|
describe('Header', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
StaticQuery.mockImplementation(({ render }) => render({ ...data }))
|
||||||
|
useStaticQuery.mockImplementation(() => ({ ...data }))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders correctly', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<Provider value={{ dark: false, toggleDark: () => null }}>
|
||||||
|
<Header />
|
||||||
|
</Provider>
|
||||||
|
)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
@ -5,9 +5,7 @@ import { useMeta } from './use-meta'
|
|||||||
import data from '../../jest/__fixtures__/meta.json'
|
import data from '../../jest/__fixtures__/meta.json'
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
useStaticQuery.mockImplementationOnce(() => {
|
useStaticQuery.mockImplementationOnce(() => ({ ...data }))
|
||||||
return { ...data }
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('useMeta', () => {
|
describe('useMeta', () => {
|
||||||
|
73
src/store/Provider.jsx
Normal file
73
src/store/Provider.jsx
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import React, { PureComponent } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { Provider } from './createContext'
|
||||||
|
import { getLocationTimes } from '../utils/getLocationTimes'
|
||||||
|
import { getCountry } from '../utils/getCountry'
|
||||||
|
|
||||||
|
export default class AppProvider extends PureComponent {
|
||||||
|
state = {
|
||||||
|
dark: false,
|
||||||
|
toggleDark: () => this.toggleDark,
|
||||||
|
location: null
|
||||||
|
}
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
children: PropTypes.any.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
store = typeof localStorage === 'undefined' ? null : localStorage
|
||||||
|
|
||||||
|
mounted = false
|
||||||
|
|
||||||
|
async componentDidMount() {
|
||||||
|
this.mounted = true
|
||||||
|
const location = await getCountry()
|
||||||
|
this.setState({ location })
|
||||||
|
this.checkDark()
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.mounted = false
|
||||||
|
}
|
||||||
|
|
||||||
|
setDark() {
|
||||||
|
this.mounted && this.setState({ dark: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
setLight() {
|
||||||
|
this.mounted && this.setState({ dark: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
darkLocalStorageMode(darkLocalStorage) {
|
||||||
|
darkLocalStorage === 'true' ? this.setDark() : this.setLight()
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// All the checks to see if we should go dark or light
|
||||||
|
//
|
||||||
|
async checkTimes() {
|
||||||
|
const { location, dark } = this.state
|
||||||
|
const { sunset, sunrise } = await getLocationTimes(location)
|
||||||
|
const now = new Date().getHours()
|
||||||
|
const weWantItDarkTimes = now >= sunset || now <= sunrise
|
||||||
|
|
||||||
|
!dark && weWantItDarkTimes ? this.setDark() : this.setLight()
|
||||||
|
}
|
||||||
|
|
||||||
|
async checkDark() {
|
||||||
|
const darkLocalStorage = await this.store.getItem('dark')
|
||||||
|
|
||||||
|
darkLocalStorage
|
||||||
|
? this.darkLocalStorageMode(darkLocalStorage)
|
||||||
|
: this.checkTimes()
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleDark = () => {
|
||||||
|
this.setState({ dark: !this.state.dark })
|
||||||
|
this.store && this.store.setItem('dark', !this.state.dark)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <Provider value={this.state}>{this.props.children}</Provider>
|
||||||
|
}
|
||||||
|
}
|
11
src/store/Provider.test.jsx
Normal file
11
src/store/Provider.test.jsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render } from 'react-testing-library'
|
||||||
|
import AppProvider from './Provider.jsx'
|
||||||
|
|
||||||
|
describe('AppProvider', () => {
|
||||||
|
it('renders correctly', () => {
|
||||||
|
const { container } = render(<AppProvider>Hello</AppProvider>)
|
||||||
|
|
||||||
|
expect(container.firstChild.textContent).toBe('Hello')
|
||||||
|
})
|
||||||
|
})
|
@ -1,120 +0,0 @@
|
|||||||
import React, { PureComponent } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import SunCalc from 'suncalc'
|
|
||||||
import { Provider } from './createContext'
|
|
||||||
import countrycodes from './countrycode-latlong.json'
|
|
||||||
|
|
||||||
export default class AppProvider extends PureComponent {
|
|
||||||
state = {
|
|
||||||
dark: false,
|
|
||||||
toggleDark: () => this.toggleDark,
|
|
||||||
location: null
|
|
||||||
}
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
children: PropTypes.any.isRequired
|
|
||||||
}
|
|
||||||
|
|
||||||
store = typeof localStorage === 'undefined' ? null : localStorage
|
|
||||||
|
|
||||||
//
|
|
||||||
// Get user location from Cloudflare's geo location HTTP header
|
|
||||||
//
|
|
||||||
getCountry = async () => {
|
|
||||||
let trace = []
|
|
||||||
|
|
||||||
try {
|
|
||||||
const data = await fetch('/cdn-cgi/trace?no-cache=1')
|
|
||||||
const text = await data.text()
|
|
||||||
const lines = text.split('\n')
|
|
||||||
|
|
||||||
let keyValue
|
|
||||||
|
|
||||||
lines.forEach(line => {
|
|
||||||
keyValue = line.split('=')
|
|
||||||
trace[keyValue[0]] = decodeURIComponent(keyValue[1] || '')
|
|
||||||
|
|
||||||
if (keyValue[0] === 'loc' && trace['loc'] !== 'XX') {
|
|
||||||
this.setState({ location: trace['loc'] })
|
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
return null // fail silently
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setDark() {
|
|
||||||
this.setState({ dark: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
setLight() {
|
|
||||||
this.setState({ dark: false })
|
|
||||||
}
|
|
||||||
|
|
||||||
darkLocalStorageMode(darkLocalStorage) {
|
|
||||||
if (darkLocalStorage === 'true') {
|
|
||||||
this.setDark()
|
|
||||||
} else {
|
|
||||||
this.setLight()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// All the checks to see if we should go dark or light
|
|
||||||
//
|
|
||||||
darkMode() {
|
|
||||||
const now = new Date().getHours()
|
|
||||||
const { location } = this.state
|
|
||||||
// fallback times, in hours
|
|
||||||
let sunrise = 7
|
|
||||||
let sunset = 19
|
|
||||||
|
|
||||||
// times based on detected country code
|
|
||||||
if (location && location !== 'XX' && location !== 'T1') {
|
|
||||||
const country = this.state.location.toLowerCase()
|
|
||||||
const times = SunCalc.getTimes(
|
|
||||||
new Date(),
|
|
||||||
countrycodes[country][0],
|
|
||||||
countrycodes[country][1]
|
|
||||||
)
|
|
||||||
sunrise = times.sunrise.getHours()
|
|
||||||
sunset = times.sunset.getHours()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.state.dark && (now >= sunset || now <= sunrise)) {
|
|
||||||
this.setDark()
|
|
||||||
} else {
|
|
||||||
this.setLight()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checkDark() {
|
|
||||||
const darkLocalStorage = this.store.getItem('dark')
|
|
||||||
|
|
||||||
if (darkLocalStorage) {
|
|
||||||
this.darkLocalStorageMode(darkLocalStorage)
|
|
||||||
} else {
|
|
||||||
this.darkMode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleDark = () => {
|
|
||||||
this.setState({ dark: !this.state.dark })
|
|
||||||
|
|
||||||
if (this.store) {
|
|
||||||
this.store.setItem('dark', !this.state.dark)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.getCountry().then(() => {
|
|
||||||
this.checkDark()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return <Provider value={this.state}>{this.props.children}</Provider>
|
|
||||||
}
|
|
||||||
}
|
|
28
src/utils/getCountry.js
Normal file
28
src/utils/getCountry.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
//
|
||||||
|
// Get user location from Cloudflare's geo location HTTP header
|
||||||
|
//
|
||||||
|
// @returns country: string
|
||||||
|
//
|
||||||
|
export const getCountry = async () => {
|
||||||
|
try {
|
||||||
|
const data = await fetch('/cdn-cgi/trace?no-cache=1')
|
||||||
|
const text = await data.text().replace(/ /g, '')
|
||||||
|
const lines = text.split('\n')
|
||||||
|
|
||||||
|
let keyValue
|
||||||
|
let trace = []
|
||||||
|
|
||||||
|
await lines.forEach(line => {
|
||||||
|
keyValue = line.split('=')
|
||||||
|
trace[keyValue[0]] = decodeURIComponent(keyValue[1] || '')
|
||||||
|
})
|
||||||
|
|
||||||
|
const country = trace['loc']
|
||||||
|
|
||||||
|
if (country && country !== 'XX') {
|
||||||
|
return country
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null // fail silently
|
||||||
|
}
|
||||||
|
}
|
27
src/utils/getCountry.test.js
Normal file
27
src/utils/getCountry.test.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { getCountry } from './getCountry'
|
||||||
|
|
||||||
|
const responseMock = 'loc=DE'
|
||||||
|
|
||||||
|
const mockFetch = data =>
|
||||||
|
jest.fn().mockImplementationOnce(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
ok: true,
|
||||||
|
Id: '123',
|
||||||
|
text: () => data
|
||||||
|
// json: () => data
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
describe('getCountry', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
global.fetch = mockFetch(responseMock)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('fetches and returns correct value', async () => {
|
||||||
|
const country = await getCountry()
|
||||||
|
|
||||||
|
expect(global.fetch).toHaveBeenCalledTimes(1)
|
||||||
|
expect(global.fetch).toHaveBeenCalledWith('/cdn-cgi/trace?no-cache=1')
|
||||||
|
expect(country).toBe('DE')
|
||||||
|
})
|
||||||
|
})
|
25
src/utils/getLocationTimes.js
Normal file
25
src/utils/getLocationTimes.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import SunCalc from 'suncalc'
|
||||||
|
import countrycodes from './countrycode-latlong.json'
|
||||||
|
|
||||||
|
//
|
||||||
|
// All the checks to see if we should go dark or light
|
||||||
|
//
|
||||||
|
export const getLocationTimes = location => {
|
||||||
|
// fallback times, in hours
|
||||||
|
let sunrise = 7
|
||||||
|
let sunset = 19
|
||||||
|
|
||||||
|
// times based on detected country code
|
||||||
|
if (location && location !== 'XX' && location !== 'T1') {
|
||||||
|
const country = location.toLowerCase()
|
||||||
|
const times = SunCalc.getTimes(
|
||||||
|
new Date(),
|
||||||
|
countrycodes[country][0],
|
||||||
|
countrycodes[country][1]
|
||||||
|
)
|
||||||
|
sunrise = times.sunrise.getHours()
|
||||||
|
sunset = times.sunset.getHours()
|
||||||
|
}
|
||||||
|
|
||||||
|
return { sunrise, sunset }
|
||||||
|
}
|
9
src/utils/getLocationTimes.test.js
Normal file
9
src/utils/getLocationTimes.test.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { getLocationTimes } from './getLocationTimes'
|
||||||
|
|
||||||
|
describe('getLocationTimes', () => {
|
||||||
|
it('returns values', async () => {
|
||||||
|
const { sunset, sunrise } = await getLocationTimes('DE')
|
||||||
|
expect(sunset).toBeDefined()
|
||||||
|
expect(sunrise).toBeDefined()
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user