diff --git a/ui/extractedTranslations/da/translation.json b/ui/extractedTranslations/da/translation.json index bfa96825..1f46bd61 100644 --- a/ui/extractedTranslations/da/translation.json +++ b/ui/extractedTranslations/da/translation.json @@ -17,6 +17,7 @@ "add": "Tilføj", "cancel": "Annuller", "delete": "Slet", + "more": "Mere", "remove": "Fjern", "save": "Gem" }, @@ -76,7 +77,7 @@ "title": "Billeder" }, "places_page": { - "title": null + "title": "Kort" }, "routes": { "page_not_found": "Side ikke fundet" @@ -169,7 +170,7 @@ }, "sidebar": { "album": { - "title": "Album indstillinger" + "title": "Indstillinger for album" }, "download": { "filesize": { @@ -223,7 +224,7 @@ "flash": "Blitz", "focal_length": "Brændvidde", "iso": "ISO", - "lens": "Lense", + "lens": "Objektiv", "maker": "Mærke" } } diff --git a/ui/extractedTranslations/en/translation.json b/ui/extractedTranslations/en/translation.json index 820513e0..418843d2 100644 --- a/ui/extractedTranslations/en/translation.json +++ b/ui/extractedTranslations/en/translation.json @@ -17,6 +17,7 @@ "add": "Add", "cancel": "Cancel", "delete": "Delete", + "more": "More", "remove": "Remove", "save": "Save" }, diff --git a/ui/extractedTranslations/fr/translation.json b/ui/extractedTranslations/fr/translation.json index 5a533d20..94d9d225 100644 --- a/ui/extractedTranslations/fr/translation.json +++ b/ui/extractedTranslations/fr/translation.json @@ -17,6 +17,7 @@ "add": "Ajouter", "cancel": "Annuler", "delete": "Effacer", + "more": null, "remove": "Supprimer", "save": "Sauvegarder" }, diff --git a/ui/extractedTranslations/sv/translation.json b/ui/extractedTranslations/sv/translation.json index 72be0a1c..dd8bbee5 100644 --- a/ui/extractedTranslations/sv/translation.json +++ b/ui/extractedTranslations/sv/translation.json @@ -17,6 +17,7 @@ "add": "Lägg till", "cancel": "Avbryt", "delete": "Radera", + "more": null, "remove": "Ta bort", "save": "Spara" }, diff --git a/ui/src/components/sidebar/MediaSidebar.tsx b/ui/src/components/sidebar/MediaSidebar.tsx index 80b28e6d..dedb8f85 100644 --- a/ui/src/components/sidebar/MediaSidebar.tsx +++ b/ui/src/components/sidebar/MediaSidebar.tsx @@ -24,6 +24,8 @@ import { sidebarPhoto_media_videoMetadata, } from './__generated__/sidebarPhoto' +import { sidebarDownloadQuery_media_downloads } from './__generated__/sidebarDownloadQuery' + const SIDEBAR_MEDIA_QUERY = gql` query sidebarPhoto($id: ID!) { media(id: $id) { @@ -161,13 +163,15 @@ export const MetadataInfo = ({ media }: MediaInfoProps) => { x => mediaExif[x] !== null && x != '__typename' ) - const exif = exifKeys.reduce( - (prev, curr) => ({ + const exif = exifKeys.reduce((prev, curr) => { + const value = mediaExif[curr] + if (isNil(value)) return prev + + return { ...prev, - [curr]: mediaExif[curr], - }), - {} as { [key: string]: string | number | null } - ) + [curr]: value, + } + }, {} as { [key: string]: string | number }) if (!isNil(exif.dateShot)) { exif.dateShot = new Date(exif.dateShot).toLocaleString() @@ -202,7 +206,7 @@ export const MetadataInfo = ({ media }: MediaInfoProps) => { } exifItems = exifKeys.map(key => ( - + )) } @@ -214,13 +218,15 @@ export const MetadataInfo = ({ media }: MediaInfoProps) => { let metadata = Object.keys(videoMetadata) .filter(x => !['id', '__typename', 'width', 'height'].includes(x)) - .reduce( - (prev, curr) => ({ + .reduce((prev, curr) => { + const value = videoMetadata[curr as string] + if (isNil(value)) return prev + + return { ...prev, - [curr]: videoMetadata[curr as string], - }), - {} as { [key: string]: string | number | null } - ) + [curr]: value, + } + }, {} as { [key: string]: string | number }) metadata = { dimensions: `${media.videoMetadata.width}x${media.videoMetadata.height}`, @@ -228,7 +234,7 @@ export const MetadataInfo = ({ media }: MediaInfoProps) => { } videoMetadataItems = Object.keys(metadata).map(key => ( - + )) } @@ -376,7 +382,7 @@ const SidebarContent = ({ media, hidePreview }: SidebarContentProps) => { )} {media.title} - + ) @@ -403,6 +409,7 @@ export interface MediaSidebarMedia { videoMetadata?: sidebarPhoto_media_videoMetadata | null exif?: sidebarPhoto_media_exif | null faces?: sidebarPhoto_media_faces[] + downloads?: sidebarDownloadQuery_media_downloads[] } type MediaSidebarType = { diff --git a/ui/src/components/sidebar/Sharing.tsx b/ui/src/components/sidebar/Sharing.tsx index 6c7a272e..457bbeb4 100644 --- a/ui/src/components/sidebar/Sharing.tsx +++ b/ui/src/components/sidebar/Sharing.tsx @@ -211,7 +211,7 @@ const ShareItemMoreDropdown = ({ // onClose={() => setDropdownOpen(false)} // open={dropdownOpen} button - text="More" + text={t('general.action.more', 'More')} closeOnChange={false} closeOnBlur={false} > diff --git a/ui/src/components/sidebar/SidebarDownload.js b/ui/src/components/sidebar/SidebarDownload.tsx similarity index 68% rename from ui/src/components/sidebar/SidebarDownload.js rename to ui/src/components/sidebar/SidebarDownload.tsx index 9e6c5384..e50f7887 100644 --- a/ui/src/components/sidebar/SidebarDownload.js +++ b/ui/src/components/sidebar/SidebarDownload.tsx @@ -6,6 +6,13 @@ import { MessageState } from '../messages/Messages' import { useLazyQuery, gql } from '@apollo/client' import { authToken } from '../../helpers/authentication' import { useTranslation } from 'react-i18next' +import { TranslationFn } from '../../localization' +import { MediaSidebarMedia } from './MediaSidebar' +import { + sidebarDownloadQuery, + sidebarDownloadQueryVariables, + sidebarDownloadQuery_media_downloads, +} from './__generated__/sidebarDownloadQuery' export const SIDEBAR_DOWNLOAD_QUERY = gql` query sidebarDownloadQuery($mediaId: ID!) { @@ -24,10 +31,12 @@ export const SIDEBAR_DOWNLOAD_QUERY = gql` } ` -const formatBytes = t => bytes => { - if (bytes == 0) return '0 Byte' - const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))) - const count = Math.round(bytes / Math.pow(1024, i), 2) +const formatBytes = (t: TranslationFn) => (bytes: number) => { + if (bytes == 0) + return t('sidebar.download.filesize.byte', '{{count}} Byte', { count: 0 }) + + const i = Math.floor(Math.log(bytes) / Math.log(1024)) + const count = Math.round(bytes / Math.pow(1024, i)) switch (i) { case 0: @@ -46,7 +55,7 @@ const formatBytes = t => bytes => { } } -const downloadMedia = t => async url => { +const downloadMedia = (t: TranslationFn) => async (url: string) => { const imgUrl = new URL(url, location.origin) if (authToken() == null) { @@ -68,15 +77,32 @@ const downloadMedia = t => async url => { blob = await response.blob() } - const filename = url.match(/[^/]*$/)[0] + if (blob == null) { + console.log('Blob is null canceling') + return + } + const filenameMatch = url.match(/[^/]*$/) + + if (filenameMatch == null) { + console.error('Could not extract filename', url) + return + } + + const filename = filenameMatch[0] downloadBlob(blob, filename) } -const downloadMediaShowProgress = t => async response => { +const downloadMediaShowProgress = (t: TranslationFn) => async ( + response: Response +) => { const totalBytes = Number(response.headers.get('content-length')) - const reader = response.body.getReader() - let data = new Uint8Array(totalBytes) + const reader = response.body?.getReader() + const data = new Uint8Array(totalBytes) + + if (reader == null) { + throw new Error('Download reader is null') + } let canceled = false const onDismiss = () => { @@ -84,9 +110,9 @@ const downloadMediaShowProgress = t => async response => { reader.cancel('Download canceled by user') } - const notifKey = Math.random().toString(26) + const notifyKey = Math.random().toString(26) MessageState.add({ - key: notifKey, + key: notifyKey, type: 'progress', onDismiss, props: { @@ -108,7 +134,7 @@ const downloadMediaShowProgress = t => async response => { receivedBytes += result.value ? result.value.length : 0 MessageState.add({ - key: notifKey, + key: notifyKey, type: 'progress', onDismiss, props: { @@ -126,7 +152,7 @@ const downloadMediaShowProgress = t => async response => { } MessageState.add({ - key: notifKey, + key: notifyKey, type: 'progress', props: { header: 'Downloading photo completed', @@ -137,20 +163,20 @@ const downloadMediaShowProgress = t => async response => { }) setTimeout(() => { - MessageState.removeKey(notifKey) + MessageState.removeKey(notifyKey) }, 2000) const content = new Blob([data.buffer], { - type: response.headers.get('content-type'), + type: response.headers.get('content-type') || undefined, }) return content } -const downloadBlob = async (blob, filename) => { - let objectUrl = window.URL.createObjectURL(blob) +const downloadBlob = async (blob: Blob, filename: string) => { + const objectUrl = window.URL.createObjectURL(blob) - let anchor = document.createElement('a') + const anchor = document.createElement('a') document.body.appendChild(anchor) anchor.href = objectUrl @@ -166,36 +192,43 @@ const DownloadTableRow = styled(Table.Row)` cursor: pointer; ` -const SidebarDownload = ({ photo }) => { +type SidebarDownladProps = { + media: MediaSidebarMedia +} + +const SidebarDownload = ({ media }: SidebarDownladProps) => { const { t } = useTranslation() - if (!photo || !photo.id) return null + if (!media || !media.id) return null - const [ - loadPhotoDownloads, - { called, loading, data }, - ] = useLazyQuery(SIDEBAR_DOWNLOAD_QUERY, { variables: { mediaId: photo.id } }) + const [loadPhotoDownloads, { called, loading, data }] = useLazyQuery< + sidebarDownloadQuery, + sidebarDownloadQueryVariables + >(SIDEBAR_DOWNLOAD_QUERY, { variables: { mediaId: media.id } }) - let downloads = [] + let downloads: sidebarDownloadQuery_media_downloads[] = [] if (called) { if (!loading) { - downloads = data && data.media.downloads + downloads = (data && data.media.downloads) || [] } } else { - if (!photo.downloads) { + if (!media.downloads) { loadPhotoDownloads() } else { - downloads = photo.downloads + downloads = media.downloads } } - const extractExtension = url => { - return url.split(/[#?]/)[0].split('.').pop().trim().toLowerCase() + const extractExtension = (url: string) => { + const urlMatch = url.split(/[#?]/) + if (urlMatch == null) return + + return urlMatch[0].split('.').pop()?.trim().toLowerCase() } const download = downloadMedia(t) const bytes = formatBytes(t) - let downloadRows = downloads.map(x => ( + const downloadRows = downloads.map(x => ( download(x.mediaUrl.url)} diff --git a/ui/src/components/sidebar/SidebarItem.js b/ui/src/components/sidebar/SidebarItem.tsx similarity index 71% rename from ui/src/components/sidebar/SidebarItem.js rename to ui/src/components/sidebar/SidebarItem.tsx index 4d5f4d08..1608f733 100644 --- a/ui/src/components/sidebar/SidebarItem.js +++ b/ui/src/components/sidebar/SidebarItem.tsx @@ -1,5 +1,4 @@ import React from 'react' -import PropTypes from 'prop-types' import styled from 'styled-components' const ItemName = styled.div` @@ -17,16 +16,16 @@ const ItemValue = styled.div` font-size: 1rem; ` -const SidebarItem = ({ name, value }) => ( +type SidebarItemProps = { + name: string + value: string +} + +const SidebarItem = ({ name, value }: SidebarItemProps) => (
{name} {value}
) -SidebarItem.propTypes = { - name: PropTypes.string.isRequired, - value: PropTypes.any.isRequired, -} - export default SidebarItem diff --git a/ui/tsconfig.json b/ui/tsconfig.json index 07e7d9c0..49652a22 100644 --- a/ui/tsconfig.json +++ b/ui/tsconfig.json @@ -3,7 +3,7 @@ /* Visit https://aka.ms/tsconfig.json to read more about this file */ /* Basic Options */ - "incremental": true /* Enable incremental compilation */, + // "incremental": true /* Enable incremental compilation */, "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, "lib": [