Rewrite sidebar to Typescript

This commit is contained in:
viktorstrate
2021-04-19 13:18:15 +02:00
parent 1430e9da02
commit 74cf35ef82
9 changed files with 101 additions and 58 deletions

View File

@@ -17,6 +17,7 @@
"add": "Tilføj", "add": "Tilføj",
"cancel": "Annuller", "cancel": "Annuller",
"delete": "Slet", "delete": "Slet",
"more": "Mere",
"remove": "Fjern", "remove": "Fjern",
"save": "Gem" "save": "Gem"
}, },
@@ -76,7 +77,7 @@
"title": "Billeder" "title": "Billeder"
}, },
"places_page": { "places_page": {
"title": null "title": "Kort"
}, },
"routes": { "routes": {
"page_not_found": "Side ikke fundet" "page_not_found": "Side ikke fundet"
@@ -169,7 +170,7 @@
}, },
"sidebar": { "sidebar": {
"album": { "album": {
"title": "Album indstillinger" "title": "Indstillinger for album"
}, },
"download": { "download": {
"filesize": { "filesize": {
@@ -223,7 +224,7 @@
"flash": "Blitz", "flash": "Blitz",
"focal_length": "Brændvidde", "focal_length": "Brændvidde",
"iso": "ISO", "iso": "ISO",
"lens": "Lense", "lens": "Objektiv",
"maker": "Mærke" "maker": "Mærke"
} }
} }

View File

@@ -17,6 +17,7 @@
"add": "Add", "add": "Add",
"cancel": "Cancel", "cancel": "Cancel",
"delete": "Delete", "delete": "Delete",
"more": "More",
"remove": "Remove", "remove": "Remove",
"save": "Save" "save": "Save"
}, },

View File

@@ -17,6 +17,7 @@
"add": "Ajouter", "add": "Ajouter",
"cancel": "Annuler", "cancel": "Annuler",
"delete": "Effacer", "delete": "Effacer",
"more": null,
"remove": "Supprimer", "remove": "Supprimer",
"save": "Sauvegarder" "save": "Sauvegarder"
}, },

View File

@@ -17,6 +17,7 @@
"add": "Lägg till", "add": "Lägg till",
"cancel": "Avbryt", "cancel": "Avbryt",
"delete": "Radera", "delete": "Radera",
"more": null,
"remove": "Ta bort", "remove": "Ta bort",
"save": "Spara" "save": "Spara"
}, },

View File

@@ -24,6 +24,8 @@ import {
sidebarPhoto_media_videoMetadata, sidebarPhoto_media_videoMetadata,
} from './__generated__/sidebarPhoto' } from './__generated__/sidebarPhoto'
import { sidebarDownloadQuery_media_downloads } from './__generated__/sidebarDownloadQuery'
const SIDEBAR_MEDIA_QUERY = gql` const SIDEBAR_MEDIA_QUERY = gql`
query sidebarPhoto($id: ID!) { query sidebarPhoto($id: ID!) {
media(id: $id) { media(id: $id) {
@@ -161,13 +163,15 @@ export const MetadataInfo = ({ media }: MediaInfoProps) => {
x => mediaExif[x] !== null && x != '__typename' x => mediaExif[x] !== null && x != '__typename'
) )
const exif = exifKeys.reduce( const exif = exifKeys.reduce((prev, curr) => {
(prev, curr) => ({ const value = mediaExif[curr]
if (isNil(value)) return prev
return {
...prev, ...prev,
[curr]: mediaExif[curr], [curr]: value,
}), }
{} as { [key: string]: string | number | null } }, {} as { [key: string]: string | number })
)
if (!isNil(exif.dateShot)) { if (!isNil(exif.dateShot)) {
exif.dateShot = new Date(exif.dateShot).toLocaleString() exif.dateShot = new Date(exif.dateShot).toLocaleString()
@@ -202,7 +206,7 @@ export const MetadataInfo = ({ media }: MediaInfoProps) => {
} }
exifItems = exifKeys.map(key => ( exifItems = exifKeys.map(key => (
<SidebarItem key={key} name={exifName[key]} value={exif[key]} /> <SidebarItem key={key} name={exifName[key]} value={exif[key] as string} />
)) ))
} }
@@ -214,13 +218,15 @@ export const MetadataInfo = ({ media }: MediaInfoProps) => {
let metadata = Object.keys(videoMetadata) let metadata = Object.keys(videoMetadata)
.filter(x => !['id', '__typename', 'width', 'height'].includes(x)) .filter(x => !['id', '__typename', 'width', 'height'].includes(x))
.reduce( .reduce((prev, curr) => {
(prev, curr) => ({ const value = videoMetadata[curr as string]
if (isNil(value)) return prev
return {
...prev, ...prev,
[curr]: videoMetadata[curr as string], [curr]: value,
}), }
{} as { [key: string]: string | number | null } }, {} as { [key: string]: string | number })
)
metadata = { metadata = {
dimensions: `${media.videoMetadata.width}x${media.videoMetadata.height}`, dimensions: `${media.videoMetadata.width}x${media.videoMetadata.height}`,
@@ -228,7 +234,7 @@ export const MetadataInfo = ({ media }: MediaInfoProps) => {
} }
videoMetadataItems = Object.keys(metadata).map(key => ( videoMetadataItems = Object.keys(metadata).map(key => (
<SidebarItem key={key} name={key} value={metadata[key]} /> <SidebarItem key={key} name={key} value={metadata[key] as string} />
)) ))
} }
@@ -376,7 +382,7 @@ const SidebarContent = ({ media, hidePreview }: SidebarContentProps) => {
)} )}
<Name>{media.title}</Name> <Name>{media.title}</Name>
<MetadataInfo media={media} /> <MetadataInfo media={media} />
<SidebarDownload photo={media} /> <SidebarDownload media={media} />
<SidebarPhotoShare id={media.id} /> <SidebarPhotoShare id={media.id} />
</div> </div>
) )
@@ -403,6 +409,7 @@ export interface MediaSidebarMedia {
videoMetadata?: sidebarPhoto_media_videoMetadata | null videoMetadata?: sidebarPhoto_media_videoMetadata | null
exif?: sidebarPhoto_media_exif | null exif?: sidebarPhoto_media_exif | null
faces?: sidebarPhoto_media_faces[] faces?: sidebarPhoto_media_faces[]
downloads?: sidebarDownloadQuery_media_downloads[]
} }
type MediaSidebarType = { type MediaSidebarType = {

View File

@@ -211,7 +211,7 @@ const ShareItemMoreDropdown = ({
// onClose={() => setDropdownOpen(false)} // onClose={() => setDropdownOpen(false)}
// open={dropdownOpen} // open={dropdownOpen}
button button
text="More" text={t('general.action.more', 'More')}
closeOnChange={false} closeOnChange={false}
closeOnBlur={false} closeOnBlur={false}
> >

View File

@@ -6,6 +6,13 @@ import { MessageState } from '../messages/Messages'
import { useLazyQuery, gql } from '@apollo/client' import { useLazyQuery, gql } from '@apollo/client'
import { authToken } from '../../helpers/authentication' import { authToken } from '../../helpers/authentication'
import { useTranslation } from 'react-i18next' 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` export const SIDEBAR_DOWNLOAD_QUERY = gql`
query sidebarDownloadQuery($mediaId: ID!) { query sidebarDownloadQuery($mediaId: ID!) {
@@ -24,10 +31,12 @@ export const SIDEBAR_DOWNLOAD_QUERY = gql`
} }
` `
const formatBytes = t => bytes => { const formatBytes = (t: TranslationFn) => (bytes: number) => {
if (bytes == 0) return '0 Byte' if (bytes == 0)
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))) return t('sidebar.download.filesize.byte', '{{count}} Byte', { count: 0 })
const count = Math.round(bytes / Math.pow(1024, i), 2)
const i = Math.floor(Math.log(bytes) / Math.log(1024))
const count = Math.round(bytes / Math.pow(1024, i))
switch (i) { switch (i) {
case 0: 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) const imgUrl = new URL(url, location.origin)
if (authToken() == null) { if (authToken() == null) {
@@ -68,15 +77,32 @@ const downloadMedia = t => async url => {
blob = await response.blob() 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) downloadBlob(blob, filename)
} }
const downloadMediaShowProgress = t => async response => { const downloadMediaShowProgress = (t: TranslationFn) => async (
response: Response
) => {
const totalBytes = Number(response.headers.get('content-length')) const totalBytes = Number(response.headers.get('content-length'))
const reader = response.body.getReader() const reader = response.body?.getReader()
let data = new Uint8Array(totalBytes) const data = new Uint8Array(totalBytes)
if (reader == null) {
throw new Error('Download reader is null')
}
let canceled = false let canceled = false
const onDismiss = () => { const onDismiss = () => {
@@ -84,9 +110,9 @@ const downloadMediaShowProgress = t => async response => {
reader.cancel('Download canceled by user') reader.cancel('Download canceled by user')
} }
const notifKey = Math.random().toString(26) const notifyKey = Math.random().toString(26)
MessageState.add({ MessageState.add({
key: notifKey, key: notifyKey,
type: 'progress', type: 'progress',
onDismiss, onDismiss,
props: { props: {
@@ -108,7 +134,7 @@ const downloadMediaShowProgress = t => async response => {
receivedBytes += result.value ? result.value.length : 0 receivedBytes += result.value ? result.value.length : 0
MessageState.add({ MessageState.add({
key: notifKey, key: notifyKey,
type: 'progress', type: 'progress',
onDismiss, onDismiss,
props: { props: {
@@ -126,7 +152,7 @@ const downloadMediaShowProgress = t => async response => {
} }
MessageState.add({ MessageState.add({
key: notifKey, key: notifyKey,
type: 'progress', type: 'progress',
props: { props: {
header: 'Downloading photo completed', header: 'Downloading photo completed',
@@ -137,20 +163,20 @@ const downloadMediaShowProgress = t => async response => {
}) })
setTimeout(() => { setTimeout(() => {
MessageState.removeKey(notifKey) MessageState.removeKey(notifyKey)
}, 2000) }, 2000)
const content = new Blob([data.buffer], { const content = new Blob([data.buffer], {
type: response.headers.get('content-type'), type: response.headers.get('content-type') || undefined,
}) })
return content return content
} }
const downloadBlob = async (blob, filename) => { const downloadBlob = async (blob: Blob, filename: string) => {
let objectUrl = window.URL.createObjectURL(blob) const objectUrl = window.URL.createObjectURL(blob)
let anchor = document.createElement('a') const anchor = document.createElement('a')
document.body.appendChild(anchor) document.body.appendChild(anchor)
anchor.href = objectUrl anchor.href = objectUrl
@@ -166,36 +192,43 @@ const DownloadTableRow = styled(Table.Row)`
cursor: pointer; cursor: pointer;
` `
const SidebarDownload = ({ photo }) => { type SidebarDownladProps = {
media: MediaSidebarMedia
}
const SidebarDownload = ({ media }: SidebarDownladProps) => {
const { t } = useTranslation() const { t } = useTranslation()
if (!photo || !photo.id) return null if (!media || !media.id) return null
const [ const [loadPhotoDownloads, { called, loading, data }] = useLazyQuery<
loadPhotoDownloads, sidebarDownloadQuery,
{ called, loading, data }, sidebarDownloadQueryVariables
] = useLazyQuery(SIDEBAR_DOWNLOAD_QUERY, { variables: { mediaId: photo.id } }) >(SIDEBAR_DOWNLOAD_QUERY, { variables: { mediaId: media.id } })
let downloads = [] let downloads: sidebarDownloadQuery_media_downloads[] = []
if (called) { if (called) {
if (!loading) { if (!loading) {
downloads = data && data.media.downloads downloads = (data && data.media.downloads) || []
} }
} else { } else {
if (!photo.downloads) { if (!media.downloads) {
loadPhotoDownloads() loadPhotoDownloads()
} else { } else {
downloads = photo.downloads downloads = media.downloads
} }
} }
const extractExtension = url => { const extractExtension = (url: string) => {
return url.split(/[#?]/)[0].split('.').pop().trim().toLowerCase() const urlMatch = url.split(/[#?]/)
if (urlMatch == null) return
return urlMatch[0].split('.').pop()?.trim().toLowerCase()
} }
const download = downloadMedia(t) const download = downloadMedia(t)
const bytes = formatBytes(t) const bytes = formatBytes(t)
let downloadRows = downloads.map(x => ( const downloadRows = downloads.map(x => (
<DownloadTableRow <DownloadTableRow
key={x.mediaUrl.url} key={x.mediaUrl.url}
onClick={() => download(x.mediaUrl.url)} onClick={() => download(x.mediaUrl.url)}

View File

@@ -1,5 +1,4 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components' import styled from 'styled-components'
const ItemName = styled.div` const ItemName = styled.div`
@@ -17,16 +16,16 @@ const ItemValue = styled.div`
font-size: 1rem; font-size: 1rem;
` `
const SidebarItem = ({ name, value }) => ( type SidebarItemProps = {
name: string
value: string
}
const SidebarItem = ({ name, value }: SidebarItemProps) => (
<div> <div>
<ItemName>{name}</ItemName> <ItemName>{name}</ItemName>
<ItemValue>{value}</ItemValue> <ItemValue>{value}</ItemValue>
</div> </div>
) )
SidebarItem.propTypes = {
name: PropTypes.string.isRequired,
value: PropTypes.any.isRequired,
}
export default SidebarItem export default SidebarItem

View File

@@ -3,7 +3,7 @@
/* Visit https://aka.ms/tsconfig.json to read more about this file */ /* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */ /* 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'. */, "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'. */, "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
"lib": [ "lib": [