diff --git a/.gitignore b/.gitignore
index 34bbc3a3..541190a5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,6 +21,7 @@ node_modules/
.cache/
dist/
build/
+*.tsbuildinfo
# misc
.DS_Store
diff --git a/ui/babel.config.js b/ui/babel.config.js
index 0f6c8a3e..38b42c43 100644
--- a/ui/babel.config.js
+++ b/ui/babel.config.js
@@ -16,7 +16,6 @@ module.exports = function (api) {
'i18next-extract',
{
locales: ['en', 'da'],
- discardOldKeys: true,
defaultValue: null,
},
])
diff --git a/ui/build.mjs b/ui/build.mjs
index 42f40066..c7b18a19 100644
--- a/ui/build.mjs
+++ b/ui/build.mjs
@@ -69,7 +69,9 @@ if (watchMode) {
bs.watch('src/**/*.@(js|tsx|ts)').on('change', async args => {
console.log('reloading', args)
builderPromise = (await builderPromise).rebuild()
- bs.reload(args)
+ setTimeout(() => {
+ bs.reload(args)
+ }, 1000)
})
} else {
const build = async () => {
diff --git a/ui/extractedTranslations/da/translation.json b/ui/extractedTranslations/da/translation.json
index 2c3f5409..01e60ac6 100644
--- a/ui/extractedTranslations/da/translation.json
+++ b/ui/extractedTranslations/da/translation.json
@@ -1,12 +1,12 @@
{
"album_filter": {
- "only_favorites": null,
- "sort_by": null,
+ "only_favorites": "Vis kun favoritter",
+ "sort_by": "Sorter efter",
"sorting_options": {
- "date_imported": null,
- "date_shot": null,
- "title": null,
- "type": null
+ "date_imported": "Dato for importering",
+ "date_shot": "Dato",
+ "title": "Titel",
+ "type": "Type"
}
},
"albums_page": {
@@ -14,33 +14,33 @@
},
"general": {
"action": {
- "add": null,
- "cancel": null,
- "delete": null,
- "remove": null,
- "save": null
+ "add": "Tilføj",
+ "cancel": "Annuller",
+ "delete": "Slet",
+ "remove": "Fjern",
+ "save": "Gem"
},
"loading": {
- "album": null,
+ "album": "Loader album",
"default": "Loader...",
- "media": null,
+ "media": "Loader medier",
"page": "Loader side",
"paginate": {
"faces": "Loader flere personer",
"media": "Loader flere medier"
},
- "shares": null,
+ "shares": "Loader delinger...",
"timeline": "Loader tidslinje"
}
},
"header": {
"search": {
- "loading": null,
- "no_results": null,
- "placeholder": null,
+ "loading": "Loader resultater...",
+ "no_results": "Fandt ingen resultater",
+ "placeholder": "Søg",
"result_type": {
- "albums": null,
- "photos": null
+ "albums": "Albums",
+ "photos": "Billeder"
}
}
},
@@ -62,6 +62,9 @@
},
"welcome": "Velkommen til Photoview"
},
+ "meta": {
+ "description": null
+ },
"people_page": {
"face_group": {
"label_placeholder": "Navn",
@@ -74,24 +77,24 @@
},
"settings": {
"concurrent_workers": {
- "description": null,
- "title": null
+ "description": "Det maksimale antal medier som må skannes samtidig",
+ "title": "Samtidige scanner-arbejdere"
},
"logout": null,
"periodic_scanner": {
- "checkbox_label": null,
+ "checkbox_label": "Aktiver periodiske scanner",
"field": {
- "description": null,
- "label": null
+ "description": "Hvor ofte scanneren bør udføre automatiske scanninger af alle brugere",
+ "label": "Periodiske scanningsintervaller"
},
"interval_unit": {
- "days": null,
- "hour": null,
- "minutes": null,
- "months": null,
- "seconds": null
+ "days": "Dage",
+ "hour": "Timer",
+ "minutes": "Minutter",
+ "months": "Måneder",
+ "seconds": "Sekunder"
},
- "title": null
+ "title": "Periodisk scanner"
},
"scanner": {
"description": "Vil scanne alle brugere for nye eller opdaterede medier",
@@ -106,21 +109,21 @@
},
"users": {
"add_user": {
- "submit": null
+ "submit": "Tilføj bruger"
},
"confirm_delete_user": {
- "action": null,
- "description": null,
- "title": null
+ "action": "Slet {user}",
+ "description": "<0>Er du sikker på at du vil slette <1>1>?0>
Denne handling kan ikke fortrydes
",
+ "title": "Slet bruger"
},
"password_reset": {
- "description": null,
+ "description": "Ændre adgangskode for <1>1>",
"form": {
- "label": null,
- "placeholder": null,
- "submit": null
+ "label": "Ny adgangskode",
+ "placeholder": "adgangskode",
+ "submit": "Ændre adgangskode"
},
- "title": null
+ "title": "Ændre adgangskode"
},
"table": {
"column_names": {
@@ -132,10 +135,10 @@
"new_user": "Ny bruger",
"row": {
"action": {
- "change_password": null,
- "delete": null,
- "edit": null,
- "scan": null
+ "change_password": "Ændre adgangskode",
+ "delete": "Slet",
+ "edit": "Rediger",
+ "scan": "Scan"
}
}
},
@@ -143,6 +146,9 @@
}
},
"share_page": {
+ "media": {
+ "title": null
+ },
"protected_share": {
"description": "Denne deling er låst med en adgangskode.",
"title": "Beskyttet deling"
@@ -153,72 +159,72 @@
},
"sidebar": {
"album": {
- "title": null
+ "title": "Album indstillinger"
},
"download": {
"filesize": {
- "byte": null,
- "byte_plural": null,
- "giga_byte": null,
- "kilo_byte": null,
- "mega_byte": null,
- "tera_byte": null
+ "byte": "{{count}} Byte",
+ "byte_plural": "{{count}} Bytes",
+ "giga_byte": "{{count}} GB",
+ "kilo_byte": "{{count}} KB",
+ "mega_byte": "{{count}} MB",
+ "tera_byte": "{{count}} TB"
},
"table_columns": {
- "dimensions": null,
- "file_size": null,
- "file_type": null,
- "name": null
+ "dimensions": "Dimension",
+ "file_size": "Størrelse",
+ "file_type": "Type",
+ "name": "Navn"
},
- "title": null
+ "title": "Download"
},
"media": {
"exif": {
"exposure_program": {
- "action_program": null,
- "aperture_priority": null,
- "bulb": null,
- "creative_program": null,
- "landscape_mode": null,
- "manual": null,
- "normal_program": null,
- "not_defined": null,
- "portrait_mode": null,
- "shutter_priority": null
+ "action_program": "Actionprogram",
+ "aperture_priority": "Blændeprioritet",
+ "bulb": "Bulb",
+ "creative_program": "Kreativ program",
+ "landscape_mode": "Landskabsmode",
+ "manual": "Manuel",
+ "normal_program": "Normal program",
+ "not_defined": "Ikke defineret",
+ "portrait_mode": "Portræt mode",
+ "shutter_priority": "Lukkerprioritet"
},
"flash": {
- "auto": null,
- "did_not_fire": null,
- "fired": null,
- "no_flash": null,
- "no_flash_function": null,
- "off": null,
- "on": null,
- "red_eye_reduction": null,
- "return_detected": null,
- "return_not_detected": null
+ "auto": "Auto",
+ "did_not_fire": "Blitz affyrede ikke",
+ "fired": "Affyrede",
+ "no_flash": "Ingen blitz",
+ "no_flash_function": "Ingen blitz-funktion",
+ "off": "Slukket",
+ "on": "Tændt",
+ "red_eye_reduction": "Røde øjne reduktion",
+ "return_detected": "Retur registreret",
+ "return_not_detected": "Retur ikke registreret"
},
"name": {
- "aperture": null,
- "camera": null,
- "date_shot": null,
- "exposure": null,
- "exposure_program": null,
- "flash": null,
- "focal_length": null,
- "iso": null,
- "lens": null,
- "maker": null
+ "aperture": "Blænde",
+ "camera": "Kamera",
+ "date_shot": "Dato",
+ "exposure": "Lukketid",
+ "exposure_program": "Lukketid program",
+ "flash": "Blitz",
+ "focal_length": "Fokallængde",
+ "iso": "ISO",
+ "lens": "Lense",
+ "maker": "Mærke"
}
}
},
"sharing": {
- "add_share": null,
- "copy_link": null,
- "no_shares_found": null,
- "public_link": null,
- "table_header": null,
- "title": null
+ "add_share": "Tilføj deling",
+ "copy_link": "Kopier link",
+ "no_shares_found": "Ingen delinger fundet",
+ "public_link": "Offentligt link",
+ "table_header": "Offentlige delinger",
+ "title": "Indstillinger for deling"
}
},
"sidemenu": {
diff --git a/ui/extractedTranslations/en/translation.json b/ui/extractedTranslations/en/translation.json
index d5f801cb..b29d3973 100644
--- a/ui/extractedTranslations/en/translation.json
+++ b/ui/extractedTranslations/en/translation.json
@@ -62,6 +62,9 @@
},
"welcome": "Welcome to Photoview"
},
+ "meta": {
+ "description": "Simple and User-friendly Photo Gallery for Personal Servers"
+ },
"people_page": {
"face_group": {
"label_placeholder": "Label",
@@ -143,6 +146,9 @@
}
},
"share_page": {
+ "media": {
+ "title": "Shared media"
+ },
"protected_share": {
"description": "This share is protected with a password.",
"title": "Protected share"
@@ -158,7 +164,7 @@
"download": {
"filesize": {
"byte": "{{count}} Byte",
- "byte_plural": null,
+ "byte_plural": "{{count}} Bytes",
"giga_byte": "{{count}} GB",
"kilo_byte": "{{count}} KB",
"mega_byte": "{{count}} MB",
diff --git a/ui/src/App.tsx b/ui/src/App.tsx
index dbbac519..25fdb5d5 100644
--- a/ui/src/App.tsx
+++ b/ui/src/App.tsx
@@ -44,6 +44,7 @@ const SITE_TRANSLATION = gql`
`
const loadTranslations = () => {
+ console.log('load translation')
const [loadLang, { data }] = useLazyQuery(SITE_TRANSLATION)
useEffect(() => {
@@ -53,13 +54,22 @@ const loadTranslations = () => {
}, [authToken()])
useEffect(() => {
+ console.log('loading translations', data)
switch (data?.myUserPreferences.language) {
case LanguageTranslation.Danish:
import('../extractedTranslations/da/translation.json').then(danish => {
+ console.log('loading danish')
i18n.addResourceBundle('da', 'translation', danish)
i18n.changeLanguage('da')
})
break
+ case LanguageTranslation.English:
+ import('../extractedTranslations/en/translation.json').then(english => {
+ console.log('loading english')
+ i18n.addResourceBundle('en', 'translation', english)
+ i18n.changeLanguage('en')
+ })
+ break
default:
i18n.changeLanguage('en')
}
diff --git a/ui/src/Layout.tsx b/ui/src/Layout.tsx
index bcb2241c..4ca3f167 100644
--- a/ui/src/Layout.tsx
+++ b/ui/src/Layout.tsx
@@ -1,4 +1,4 @@
-import React, { ReactChild, ReactChildren } from 'react'
+import React, { ReactChild } from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import { NavLink } from 'react-router-dom'
@@ -143,7 +143,7 @@ export const SideMenu = () => {
}
type LayoutProps = {
- children: ReactChild | ReactChildren
+ children: React.ReactNode
title: string
}
diff --git a/ui/src/Pages/SettingsPage/SettingsPage.js b/ui/src/Pages/SettingsPage/SettingsPage.tsx
similarity index 95%
rename from ui/src/Pages/SettingsPage/SettingsPage.js
rename to ui/src/Pages/SettingsPage/SettingsPage.tsx
index 31b80bfd..b9d02691 100644
--- a/ui/src/Pages/SettingsPage/SettingsPage.js
+++ b/ui/src/Pages/SettingsPage/SettingsPage.tsx
@@ -10,7 +10,7 @@ import ScannerSection from './ScannerSection'
import UserPreferences from './UserPreferences'
import UsersTable from './Users/UsersTable'
-export const SectionTitle = styled.h2`
+export const SectionTitle = styled.h2<{ nospace: boolean }>`
margin-top: ${({ nospace }) => (nospace ? '0' : '1.4em')} !important;
padding-bottom: 0.3em;
border-bottom: 1px solid #ddd;
diff --git a/ui/src/Pages/SettingsPage/UserPreferences.js b/ui/src/Pages/SettingsPage/UserPreferences.tsx
similarity index 67%
rename from ui/src/Pages/SettingsPage/UserPreferences.js
rename to ui/src/Pages/SettingsPage/UserPreferences.tsx
index b67d6d03..3149286c 100644
--- a/ui/src/Pages/SettingsPage/UserPreferences.js
+++ b/ui/src/Pages/SettingsPage/UserPreferences.tsx
@@ -4,12 +4,17 @@ import React from 'react'
import { useTranslation } from 'react-i18next'
import { Dropdown } from 'semantic-ui-react'
import styled from 'styled-components'
-
+import { LanguageTranslation } from '../../../__generated__/globalTypes'
import { SectionTitle } from './SettingsPage'
+import {
+ changeUserPreferences,
+ changeUserPreferencesVariables,
+} from './__generated__/changeUserPreferences'
+import { myUserPreferences } from './__generated__/myUserPreferences'
const languagePreferences = [
- { key: 1, text: 'English', value: 'en' },
- { key: 2, text: 'Dansk', value: 'da' },
+ { key: 1, text: 'English', value: LanguageTranslation.English },
+ { key: 2, text: 'Dansk', value: LanguageTranslation.Danish },
]
const CHANGE_USER_PREFERENCES = gql`
@@ -37,14 +42,15 @@ const UserPreferencesWrapper = styled.div`
const UserPreferences = () => {
const { t } = useTranslation()
- const { data } = useQuery(MY_USER_PREFERENCES)
+ const { data } = useQuery(MY_USER_PREFERENCES)
- const [changePrefs, { loading: loadingPrefs, error }] = useMutation(
- CHANGE_USER_PREFERENCES
- )
+ const [changePrefs, { loading: loadingPrefs, error }] = useMutation<
+ changeUserPreferences,
+ changeUserPreferencesVariables
+ >(CHANGE_USER_PREFERENCES)
if (error) {
- return error.message
+ return {error.message}
}
return (
@@ -62,12 +68,12 @@ const UserPreferences = () => {
onChange={(event, { value: language }) => {
changePrefs({
variables: {
- language,
+ language: language as LanguageTranslation,
},
})
}}
selection
- value={data?.myUserPreferences.language}
+ value={data?.myUserPreferences.language || undefined}
loading={loadingPrefs}
disabled={loadingPrefs}
/>
diff --git a/ui/src/Pages/SettingsPage/Users/UsersTable.js b/ui/src/Pages/SettingsPage/Users/UsersTable.js
index 90b4ca15..4d7f21f5 100644
--- a/ui/src/Pages/SettingsPage/Users/UsersTable.js
+++ b/ui/src/Pages/SettingsPage/Users/UsersTable.js
@@ -29,7 +29,7 @@ const UsersTable = () => {
const { loading, error, data, refetch } = useQuery(USERS_QUERY)
if (error) {
- return `Users table error: ${error.message}`
+ return {`Users table error: ${error.message}`}
}
let userRows = []
diff --git a/ui/src/components/AlbumTitle.tsx b/ui/src/components/AlbumTitle.tsx
index b7fd32be..4cf5604c 100644
--- a/ui/src/components/AlbumTitle.tsx
+++ b/ui/src/components/AlbumTitle.tsx
@@ -50,7 +50,7 @@ const ALBUM_PATH_QUERY = gql`
`
type AlbumTitleProps = {
- album: {
+ album?: {
id: string
title: string
}
diff --git a/ui/src/components/albumGallery/AlbumGallery.tsx b/ui/src/components/albumGallery/AlbumGallery.tsx
index 4c9fd92f..5eeb1a6d 100644
--- a/ui/src/components/albumGallery/AlbumGallery.tsx
+++ b/ui/src/components/albumGallery/AlbumGallery.tsx
@@ -6,7 +6,7 @@ import AlbumFilter from '../AlbumFilter'
import { albumQuery_album } from '../../Pages/AlbumPage/__generated__/albumQuery'
type AlbumGalleryProps = {
- album: albumQuery_album
+ album?: albumQuery_album
loading?: boolean
customAlbumLink?(albumID: string): string
showFilter?: boolean
@@ -64,10 +64,13 @@ const AlbumGallery = React.forwardRef(
}
const nextImage = () => {
+ if (album === undefined) return
setActiveImage((imageState.activeImage + 1) % album.media.length)
}
const previousImage = () => {
+ if (album === undefined) return
+
if (imageState.activeImage <= 0) {
setActiveImage(album.media.length - 1)
} else {
@@ -131,7 +134,7 @@ const AlbumGallery = React.forwardRef(
}
{
diff --git a/ui/src/components/photoGallery/MediaThumbnail.tsx b/ui/src/components/photoGallery/MediaThumbnail.tsx
index 53b53970..2ddb98c8 100644
--- a/ui/src/components/photoGallery/MediaThumbnail.tsx
+++ b/ui/src/components/photoGallery/MediaThumbnail.tsx
@@ -4,6 +4,10 @@ import styled from 'styled-components'
import { Icon } from 'semantic-ui-react'
import { ProtectedImage } from './ProtectedMedia'
import { MediaType } from '../../../__generated__/globalTypes'
+import {
+ markMediaFavorite,
+ markMediaFavoriteVariables,
+} from './__generated__/markMediaFavorite'
const markFavoriteMutation = gql`
mutation markMediaFavorite($mediaId: ID!, $favorite: Boolean!) {
@@ -130,7 +134,10 @@ export const MediaThumbnail = ({
setPresenting,
onFavorite,
}: MediaThumbnailProps) => {
- const [markFavorite] = useMutation(markFavoriteMutation)
+ const [markFavorite] = useMutation<
+ markMediaFavorite,
+ markMediaFavoriteVariables
+ >(markFavoriteMutation)
let heartIcon = null
if (media.favorite !== undefined) {
diff --git a/ui/src/components/photoGallery/PhotoGallery.tsx b/ui/src/components/photoGallery/PhotoGallery.tsx
index 5d681aff..61d140c3 100644
--- a/ui/src/components/photoGallery/PhotoGallery.tsx
+++ b/ui/src/components/photoGallery/PhotoGallery.tsx
@@ -3,7 +3,7 @@ import styled from 'styled-components'
import { Loader } from 'semantic-ui-react'
import { MediaThumbnail, PhotoThumbnail } from './MediaThumbnail'
import PresentView from './presentView/PresentView'
-import { SidebarContext, UpdateSidebarFn } from '../sidebar/Sidebar'
+import { SidebarContext } from '../sidebar/Sidebar'
import MediaSidebar from '../sidebar/MediaSidebar'
import { useTranslation } from 'react-i18next'
import { PresentMediaProps_Media } from './presentView/PresentMedia'
@@ -65,44 +65,32 @@ const PhotoGallery = ({
const { t } = useTranslation()
const { updateSidebar } = useContext(SidebarContext)
- if (
- media === undefined ||
- activeIndex === -1 ||
- media[activeIndex] === undefined
- ) {
- return null
- }
+ const activeImage: PhotoGalleryProps_Media | undefined = media[activeIndex]
- const activeImage = media[activeIndex]
+ let photoElements = []
+ if (media) {
+ photoElements = media.map((photo, index) => {
+ const active = activeIndex == index
- const getPhotoElements = (updateSidebar: UpdateSidebarFn) => {
- let photoElements = []
- if (media) {
- photoElements = media.map((photo, index) => {
- const active = activeIndex == index
-
- return (
- {
- updateSidebar()
- onSelectImage(index)
- }}
- onFavorite={onFavorite}
- setPresenting={setPresenting}
- index={index}
- active={active}
- />
- )
- })
- } else {
- for (let i = 0; i < 6; i++) {
- photoElements.push()
- }
+ return (
+ {
+ updateSidebar()
+ onSelectImage(index)
+ }}
+ onFavorite={onFavorite}
+ setPresenting={setPresenting}
+ index={index}
+ active={active}
+ />
+ )
+ })
+ } else {
+ for (let i = 0; i < 6; i++) {
+ photoElements.push()
}
-
- return photoElements
}
return (
@@ -111,7 +99,7 @@ const PhotoGallery = ({
{t('general.loading.media', 'Loading media')}
- {getPhotoElements(updateSidebar)}
+ {photoElements}
{presenting && (
diff --git a/ui/src/components/photoGallery/ProtectedMedia.tsx b/ui/src/components/photoGallery/ProtectedMedia.tsx
index c53af996..87eaad7a 100644
--- a/ui/src/components/photoGallery/ProtectedMedia.tsx
+++ b/ui/src/components/photoGallery/ProtectedMedia.tsx
@@ -26,6 +26,7 @@ export interface ProtectedImageProps
src?: string
key?: string
lazyLoading?: boolean
+ loaded?: boolean
}
/**
@@ -38,6 +39,7 @@ export const ProtectedImage = ({
src,
key,
lazyLoading,
+ loaded,
...props
}: ProtectedImageProps) => {
const lazyLoadProps: { 'data-src'?: string; loading?: 'lazy' | 'eager' } = {}
@@ -55,11 +57,15 @@ export const ProtectedImage = ({
? placeholder
: getProtectedUrl(src) || placeholder
+ const loadedProp =
+ loaded !== undefined ? { loaded: loaded.toString() } : undefined
+
return (
diff --git a/ui/src/components/routes/AuthorizedRoute.tsx b/ui/src/components/routes/AuthorizedRoute.tsx
index 6779f3d5..6c0c6a41 100644
--- a/ui/src/components/routes/AuthorizedRoute.tsx
+++ b/ui/src/components/routes/AuthorizedRoute.tsx
@@ -6,10 +6,10 @@ import { authToken } from '../../helpers/authentication'
import { ADMIN_QUERY } from '../../Layout'
export const useIsAdmin = (enabled = true) => {
- const [fetchAdminQuery, { data }] = useLazyQuery(ADMIN_QUERY)
+ const [fetchAdminQuery, { data, called }] = useLazyQuery(ADMIN_QUERY)
useEffect(() => {
- if (authToken() && !data && enabled) {
+ if (authToken() && !called && enabled) {
fetchAdminQuery()
}
}, [authToken(), enabled])