1
Fork 0

Add service worker + various cleanup

This commit is contained in:
viktorstrate 2021-07-22 13:45:57 +02:00
parent 767b1f8327
commit 1748aeffb6
No known key found for this signature in database
GPG Key ID: 3F855605109C1E8A
16 changed files with 266 additions and 145 deletions

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -2,20 +2,18 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000" />
<!--
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
--> <meta name="apple-mobile-web-app-title" content="Photoview" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="white" />
<!-- <!--
manifest.json provides metadata used when your web app is installed on a manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
--> -->
<!--
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
-->
<!-- <!--
Notice the use of %PUBLIC_URL% in the tags above. Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build. It will be replaced with the URL of the `public` folder during the build.

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 103 KiB

23
ui/public/manifest.json Normal file
View File

@ -0,0 +1,23 @@
{
"name": "Photoview",
"short_name": "Photoview",
"description": "Photo gallery for self-hosted personal servers",
"start_url": "/",
"display": "standalone",
"theme_color": "black",
"background_color": "white",
"icons": [
{
"src": "/assets/logo192.png",
"sizes": "192x192"
},
{
"src": "/assets/logo512.png",
"sizes": "512x512"
},
{
"src": "/assets/photoview-logo.svg",
"sizes": "150x150"
}
]
}

View File

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

View File

@ -4,7 +4,6 @@ import { useForm, SubmitHandler } from 'react-hook-form'
import { checkInitialSetupQuery, login } from './loginUtilities' import { checkInitialSetupQuery, login } from './loginUtilities'
import { authToken } from '../../helpers/authentication' import { authToken } from '../../helpers/authentication'
import logoPath from '../../assets/photoview-logo.svg'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Helmet } from 'react-helmet' import { Helmet } from 'react-helmet'
import { Redirect } from 'react-router' import { Redirect } from 'react-router'
@ -28,7 +27,11 @@ const LogoHeader = () => {
return ( return (
<div className="flex justify-center flex-col mb-14 mt-20"> <div className="flex justify-center flex-col mb-14 mt-20">
<img className="h-24" src={logoPath} alt="photoview logo" /> <img
className="h-24"
src={process.env.PUBLIC_URL + '/photoview-logo.svg'}
alt="photoview logo"
/>
<h1 className="text-3xl text-center mt-4"> <h1 className="text-3xl text-center mt-4">
{t('login_page.welcome', 'Welcome to Photoview')} {t('login_page.welcome', 'Welcome to Photoview')}
</h1> </h1>

View File

@ -157,8 +157,7 @@ export const FaceDetails = ({ group }: FaceDetailsProps) => {
} }
onKeyDown={onKeyDown} onKeyDown={onKeyDown}
onChange={e => setInputValue(e.target.value)} onChange={e => setInputValue(e.target.value)}
onBlur={e => { onBlur={() => {
console.log(e)
resetLabel() resetLabel()
}} }}
/> />

View File

@ -39,42 +39,3 @@ export function placesReducer(
return photoGalleryReducer(state, action) return photoGalleryReducer(state, action)
} }
} }
// export const presentMarkerClicked = ({
// dispatchMedia,
// mediaState,
// marker,
// }: {
// dispatchMedia: React.Dispatch<PlacesAction>
// mediaState: PlacesState
// marker: PresentMarker
// }) => {
// console.log(
// 'present marker clicked',
// mediaState,
// marker,
// !!mediaState.presentMarker,
// mediaState.presentMarker?.cluster === marker.cluster,
// mediaState.presentMarker?.id === marker.id
// )
// if (
// mediaState.presentMarker &&
// mediaState.presentMarker.cluster === marker.cluster &&
// mediaState.presentMarker.id === marker.id
// ) {
// console.log('EQUAL OPEN PRESENT MODE')
// history.pushState(
// { presenting: true, activeIndex: mediaState.activeIndex },
// ''
// )
// dispatchMedia({
// type: 'openPresentMode',
// activeIndex: mediaState.activeIndex,
// })
// } else {
// dispatchMedia({
// type: 'replacePresentMarker',
// marker,
// })
// }
// }

View File

@ -60,7 +60,6 @@ const AlbumTitle = ({ album, disableLink = false }: AlbumTitleProps) => {
}, [album]) }, [album])
const delay = useDelay(200, [album]) const delay = useDelay(200, [album])
console.log('delay', delay)
if (!album) { if (!album) {
return ( return (

View File

@ -1,7 +1,6 @@
import React, { useContext } from 'react' import React, { useContext } from 'react'
import SearchBar from './Searchbar' import SearchBar from './Searchbar'
import logoPath from '../../assets/photoview-logo.svg'
import { authToken } from '../../helpers/authentication' import { authToken } from '../../helpers/authentication'
import { SidebarContext } from '../sidebar/Sidebar' import { SidebarContext } from '../sidebar/Sidebar'
import classNames from 'classnames' import classNames from 'classnames'
@ -17,7 +16,11 @@ const Header = () => {
)} )}
> >
<h1 className="mr-4 lg:mr-8 flex-shrink-0 flex items-center"> <h1 className="mr-4 lg:mr-8 flex-shrink-0 flex items-center">
<img className="h-12 lg:h-10" src={logoPath} alt="logo" /> <img
className="h-12 lg:h-10"
src={process.env.PUBLIC_URL + '/photoview-logo.svg'}
alt="logo"
/>
<span className="hidden lg:block ml-2 text-2xl font-light"> <span className="hidden lg:block ml-2 text-2xl font-light">
Photoview Photoview
</span> </span>

View File

@ -128,7 +128,6 @@ const SearchBar = () => {
const keydownEvent = (event: KeyboardEvent) => { const keydownEvent = (event: KeyboardEvent) => {
if (!expanded) return if (!expanded) return
console.log(event.key)
if (event.key == 'ArrowDown') { if (event.key == 'ArrowDown') {
event.preventDefault() event.preventDefault()
setSelectedItem(i => (i === null ? 0 : Math.min(totalItems - 1, i + 1))) setSelectedItem(i => (i === null ? 0 : Math.min(totalItems - 1, i + 1)))

View File

@ -3,11 +3,11 @@ import 'regenerator-runtime/runtime'
import React from 'react' import React from 'react'
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import App from './App' import App from './App'
import registerServiceWorker from './registerServiceWorker'
import client from './apolloClient' import client from './apolloClient'
import { ApolloProvider } from '@apollo/client' import { ApolloProvider } from '@apollo/client'
import { BrowserRouter as Router } from 'react-router-dom' import { BrowserRouter as Router } from 'react-router-dom'
import { setupLocalization } from './localization' import { setupLocalization } from './localization'
import * as serviceWorkerRegistration from './serviceWorkerRegistration'
import './index.css' import './index.css'
import { SidebarProvider } from './components/sidebar/Sidebar' import { SidebarProvider } from './components/sidebar/Sidebar'
@ -26,4 +26,4 @@ const Main = () => (
ReactDOM.render(<Main />, document.getElementById('root')) ReactDOM.render(<Main />, document.getElementById('root'))
registerServiceWorker() serviceWorkerRegistration.register()

View File

@ -1,91 +0,0 @@
// In production, we register a service worker to serve assets from local cache.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on the "N+1" visit to a page, since previously
// cached resources are updated in the background.
// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
// This link also includes instructions on opting out of this behavior.
const isProduction = process.env.NODE_ENV == 'production'
export default function register() {
if (isProduction && 'serviceWorker' in navigator) {
window.addEventListener('load', () => {
const swUrl = `/service-worker.js`
if (!isProduction) {
// This is running on localhost. Lets check if a service worker still exists or not.
checkValidServiceWorker(swUrl)
} else {
// Is not local host. Just register service worker
registerValidSW(swUrl)
}
})
}
}
function registerValidSW(swUrl) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the old content will have been purged and
// the fresh content will have been added to the cache.
// It's the perfect time to display a "New content is
// available; please refresh." message in your web app.
console.log('New content is available; please refresh.')
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.')
}
}
}
}
})
.catch(error => {
console.error('Error during service worker registration:', error)
})
}
function checkValidServiceWorker(swUrl) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
if (
response.status === 404 ||
response.headers.get('content-type').indexOf('javascript') === -1
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload()
})
})
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl)
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
)
})
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister()
})
}
}

81
ui/src/service-worker.ts Normal file
View File

@ -0,0 +1,81 @@
/// <reference lib="webworker" />
/* eslint-disable no-restricted-globals */
// This service worker can be customized!
// See https://developers.google.com/web/tools/workbox/modules
// for the list of available Workbox modules, or add any other
// code you'd like.
// You can also remove this file if you'd prefer not to use a
// service worker, and the Workbox build step will be skipped.
import { clientsClaim } from 'workbox-core'
import { ExpirationPlugin } from 'workbox-expiration'
import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching'
import { registerRoute } from 'workbox-routing'
import { StaleWhileRevalidate } from 'workbox-strategies'
declare const self: ServiceWorkerGlobalScope
clientsClaim()
// Precache all of the assets generated by your build process.
// Their URLs are injected into the manifest variable below.
// This variable must be present somewhere in your service worker file,
// even if you decide not to use precaching. See https://cra.link/PWA
precacheAndRoute(self.__WB_MANIFEST)
// Set up App Shell-style routing, so that all navigation requests
// are fulfilled with your index.html shell. Learn more at
// https://developers.google.com/web/fundamentals/architecture/app-shell
const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$')
registerRoute(
// Return false to exempt requests from being fulfilled by index.html.
({ request, url }: { request: Request; url: URL }) => {
// If this isn't a navigation, skip.
if (request.mode !== 'navigate') {
return false
}
// If this is a URL that starts with /_, skip.
if (url.pathname.startsWith('/_')) {
return false
}
// If this looks like a URL for a resource, because it contains
// a file extension, skip.
if (url.pathname.match(fileExtensionRegexp)) {
return false
}
// Return true to signal that we want to use the handler.
return true
},
createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html')
)
// An example runtime caching route for requests that aren't handled by the
// precache, in this case same-origin .png requests like those from in public/
registerRoute(
// Add in any other file extensions or routing criteria as needed.
({ url }) =>
url.origin === self.location.origin && url.pathname.endsWith('.png'),
// Customize this strategy as needed, e.g., by changing to CacheFirst.
new StaleWhileRevalidate({
cacheName: 'images',
plugins: [
// Ensure that once this runtime cache reaches a maximum size the
// least-recently used images are removed.
new ExpirationPlugin({ maxEntries: 50 }),
],
})
)
// This allows the web app to trigger skipWaiting via
// registration.waiting.postMessage({type: 'SKIP_WAITING'})
self.addEventListener('message', event => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting()
}
})
// Any other custom service worker logic can go here.

View File

@ -0,0 +1,146 @@
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://cra.link/PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
)
type Config = {
onSuccess?: (registration: ServiceWorkerRegistration) => void
onUpdate?: (registration: ServiceWorkerRegistration) => void
}
export function register(config?: Config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href)
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config)
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service worker.' +
'worker. To learn more, visit https://cra.link/PWA'
)
})
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config)
}
})
}
}
function registerValidSW(swUrl: string, config?: Config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing
if (installingWorker == null) {
return
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://cra.link/PWA.'
)
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration)
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.')
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration)
}
}
}
}
}
})
.catch(error => {
console.error('Error during service worker registration:', error)
})
}
function checkValidServiceWorker(swUrl: string, config?: Config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, {
headers: { 'Service-Worker': 'script' },
})
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type')
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload()
})
})
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config)
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
)
})
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready
.then(registration => {
registration.unregister()
})
.catch(error => {
console.error(error.message)
})
}
}