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>?

Denne handling kan ikke fortrydes

", + "title": "Slet bruger" }, "password_reset": { - "description": null, + "description": "Ændre adgangskode for <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])