diff --git a/api/graphql/generated.go b/api/graphql/generated.go index fd596063..c1612f72 100644 --- a/api/graphql/generated.go +++ b/api/graphql/generated.go @@ -1882,7 +1882,7 @@ type Album { thumbnail: Media path: [Album!]! - shares: [ShareToken] + shares: [ShareToken!]! } type MediaURL { @@ -3232,11 +3232,14 @@ func (ec *executionContext) _Album_shares(ctx context.Context, field graphql.Col return graphql.Null } if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } return graphql.Null } res := resTmp.([]*models.ShareToken) fc.Result = res - return ec.marshalOShareToken2ᚕᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐShareToken(ctx, field.Selections, res) + return ec.marshalNShareToken2ᚕᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐShareTokenᚄ(ctx, field.Selections, res) } func (ec *executionContext) _AuthorizeResult_success(ctx context.Context, field graphql.CollectedField, obj *models.AuthorizeResult) (ret graphql.Marshaler) { @@ -10044,6 +10047,9 @@ func (ec *executionContext) _Album(ctx context.Context, sel ast.SelectionSet, ob } }() res = ec._Album_shares(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } return res }) default: @@ -12642,53 +12648,6 @@ func (ec *executionContext) unmarshalOPagination2ᚖgithubᚗcomᚋphotoviewᚋp return &res, graphql.ErrorOnPath(ctx, err) } -func (ec *executionContext) marshalOShareToken2ᚕᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐShareToken(ctx context.Context, sel ast.SelectionSet, v []*models.ShareToken) graphql.Marshaler { - if v == nil { - return graphql.Null - } - ret := make(graphql.Array, len(v)) - var wg sync.WaitGroup - isLen1 := len(v) == 1 - if !isLen1 { - wg.Add(len(v)) - } - for i := range v { - i := i - fc := &graphql.FieldContext{ - Index: &i, - Result: &v[i], - } - ctx := graphql.WithFieldContext(ctx, fc) - f := func(i int) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = nil - } - }() - if !isLen1 { - defer wg.Done() - } - ret[i] = ec.marshalOShareToken2ᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐShareToken(ctx, sel, v[i]) - } - if isLen1 { - f(i) - } else { - go f(i) - } - - } - wg.Wait() - return ret -} - -func (ec *executionContext) marshalOShareToken2ᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐShareToken(ctx context.Context, sel ast.SelectionSet, v *models.ShareToken) graphql.Marshaler { - if v == nil { - return graphql.Null - } - return ec._ShareToken(ctx, sel, v) -} - func (ec *executionContext) unmarshalOShareTokenCredentials2ᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐShareTokenCredentials(ctx context.Context, v interface{}) (*models.ShareTokenCredentials, error) { if v == nil { return nil, nil diff --git a/api/graphql/schema.graphql b/api/graphql/schema.graphql index 224a2aa5..94538754 100644 --- a/api/graphql/schema.graphql +++ b/api/graphql/schema.graphql @@ -262,7 +262,7 @@ type Album { thumbnail: Media path: [Album!]! - shares: [ShareToken] + shares: [ShareToken!]! } type MediaURL { diff --git a/ui/src/components/sidebar/AlbumSidebar.js b/ui/src/components/sidebar/AlbumSidebar.tsx similarity index 75% rename from ui/src/components/sidebar/AlbumSidebar.js rename to ui/src/components/sidebar/AlbumSidebar.tsx index 3fab32b0..f2e457db 100644 --- a/ui/src/components/sidebar/AlbumSidebar.js +++ b/ui/src/components/sidebar/AlbumSidebar.tsx @@ -1,7 +1,6 @@ import React from 'react' -import PropTypes from 'prop-types' import { useQuery, gql } from '@apollo/client' -import SidebarShare from './Sharing' +import { SidebarAlbumShare } from './Sharing' import { useTranslation } from 'react-i18next' const albumQuery = gql` @@ -13,7 +12,11 @@ const albumQuery = gql` } ` -const AlbumSidebar = ({ albumId }) => { +type AlbumSidebarProps = { + albumId: string +} + +const AlbumSidebar = ({ albumId }: AlbumSidebarProps) => { const { t } = useTranslation() const { loading, error, data } = useQuery(albumQuery, { variables: { id: albumId }, @@ -27,14 +30,10 @@ const AlbumSidebar = ({ albumId }) => {

{t('sidebar.album.title', 'Album options')}

{data.album.title}

- +
) } -AlbumSidebar.propTypes = { - albumId: PropTypes.string.isRequired, -} - export default AlbumSidebar diff --git a/ui/src/components/sidebar/MediaSidebar.tsx b/ui/src/components/sidebar/MediaSidebar.tsx index 5de73f72..4d4b6621 100644 --- a/ui/src/components/sidebar/MediaSidebar.tsx +++ b/ui/src/components/sidebar/MediaSidebar.tsx @@ -7,7 +7,7 @@ import { ProtectedVideo, ProtectedVideoProps_Media, } from '../photoGallery/ProtectedMedia' -import SidebarShare from './Sharing' +import { SidebarPhotoShare } from './Sharing' import SidebarDownload from './SidebarDownload' import SidebarItem from './SidebarItem' import { SidebarFacesOverlay } from '../facesOverlay/FacesOverlay' @@ -377,7 +377,7 @@ const SidebarContent = ({ media, hidePreview }: SidebarContentProps) => { {media.title} - + ) } diff --git a/ui/src/components/sidebar/Sharing.js b/ui/src/components/sidebar/Sharing.tsx similarity index 50% rename from ui/src/components/sidebar/Sharing.js rename to ui/src/components/sidebar/Sharing.tsx index 0adae358..6c7a272e 100644 --- a/ui/src/components/sidebar/Sharing.js +++ b/ui/src/components/sidebar/Sharing.tsx @@ -1,5 +1,4 @@ import React, { useState } from 'react' -import PropTypes from 'prop-types' import { useMutation, useQuery, gql } from '@apollo/client' import { Table, @@ -10,12 +9,37 @@ import { Icon, } from 'semantic-ui-react' import copy from 'copy-to-clipboard' -import { authToken } from '../../helpers/authentication' import styled from 'styled-components' import { useTranslation } from 'react-i18next' +import { sidbarGetAlbumShares_album_shares } from './__generated__/sidbarGetAlbumShares' +import { + sidebareDeleteShare, + sidebareDeleteShareVariables, +} from './__generated__/sidebareDeleteShare' +import { + sidebarProtectShare, + sidebarProtectShareVariables, +} from './__generated__/sidebarProtectShare' +import { sidbarGetPhotoShares_media_shares } from './__generated__/sidbarGetPhotoShares' +import { + sidebarPhotoAddShare, + sidebarPhotoAddShareVariables, +} from './__generated__/sidebarPhotoAddShare' +import { + sidebarAlbumAddShare, + sidebarAlbumAddShareVariables, +} from './__generated__/sidebarAlbumAddShare' +import { + sidebarGetPhotoShares, + sidebarGetPhotoSharesVariables, +} from './__generated__/sidebarGetPhotoShares' +import { + sidebarGetAlbumShares, + sidebarGetAlbumSharesVariables, +} from './__generated__/sidebarGetAlbumShares' -const sharePhotoQuery = gql` - query sidbarGetPhotoShares($id: ID!) { +const SHARE_PHOTO_QUERY = gql` + query sidebarGetPhotoShares($id: ID!) { media(id: $id) { id shares { @@ -27,8 +51,8 @@ const sharePhotoQuery = gql` } ` -const shareAlbumQuery = gql` - query sidbarGetAlbumShares($id: ID!) { +const SHARE_ALBUM_QUERY = gql` + query sidebarGetAlbumShares($id: ID!) { album(id: $id) { id shares { @@ -40,7 +64,7 @@ const shareAlbumQuery = gql` } ` -const addPhotoShareMutation = gql` +const ADD_MEDIA_SHARE_MUTATION = gql` mutation sidebarPhotoAddShare($id: ID!, $password: String, $expire: Time) { shareMedia(mediaId: $id, password: $password, expire: $expire) { token @@ -48,7 +72,7 @@ const addPhotoShareMutation = gql` } ` -const addAlbumShareMutation = gql` +const ADD_ALBUM_SHARE_MUTATION = gql` mutation sidebarAlbumAddShare($id: ID!, $password: String, $expire: Time) { shareAlbum(albumId: $id, password: $password, expire: $expire) { token @@ -56,7 +80,7 @@ const addAlbumShareMutation = gql` } ` -const protectShareMutation = gql` +const PROTECT_SHARE_MUTATION = gql` mutation sidebarProtectShare($token: String!, $password: String) { protectShareToken(token: $token, password: $password) { token @@ -65,7 +89,7 @@ const protectShareMutation = gql` } ` -const deleteShareMutation = gql` +const DELETE_SHARE_MUTATION = gql` mutation sidebareDeleteShare($token: String!) { deleteShareToken(token: $token) { token @@ -73,16 +97,26 @@ const deleteShareMutation = gql` } ` -const ShareItemMoreDropdown = ({ id, share, isPhoto }) => { - const { t } = useTranslation() - const query = isPhoto ? sharePhotoQuery : shareAlbumQuery +type ShareItemMoreDropdownProps = { + id: string + isPhoto: boolean + share: sidbarGetAlbumShares_album_shares +} - const [deleteShare, { loading: deleteShareLoading }] = useMutation( - deleteShareMutation, - { - refetchQueries: [{ query: query, variables: { id } }], - } - ) +const ShareItemMoreDropdown = ({ + id, + share, + isPhoto, +}: ShareItemMoreDropdownProps) => { + const { t } = useTranslation() + const query = isPhoto ? SHARE_PHOTO_QUERY : SHARE_ALBUM_QUERY + + const [deleteShare, { loading: deleteShareLoading }] = useMutation< + sidebareDeleteShare, + sidebareDeleteShareVariables + >(DELETE_SHARE_MUTATION, { + refetchQueries: [{ query: query, variables: { id } }], + }) const [addingPassword, setAddingPassword] = useState(false) const showPasswordInput = addingPassword || share.hasPassword @@ -92,36 +126,36 @@ const ShareItemMoreDropdown = ({ id, share, isPhoto }) => { ) const [passwordHidden, setPasswordHidden] = useState(share.hasPassword) - const hidePassword = hide => { + const hidePassword = (hide: boolean) => { setPasswordHidden(hide) if (hide) { setPasswordInputValue('**********') } } - const [setPassword, { loading: setPasswordLoading }] = useMutation( - protectShareMutation, - { - refetchQueries: [{ query: query, variables: { id } }], - onCompleted: data => { - hidePassword(data.protectShareToken.hasPassword) - }, - // refetchQueries: [{ query: query, variables: { id } }], - variables: { - token: share.token, - }, - } - ) + const [setPassword, { loading: setPasswordLoading }] = useMutation< + sidebarProtectShare, + sidebarProtectShareVariables + >(PROTECT_SHARE_MUTATION, { + refetchQueries: [{ query: query, variables: { id } }], + onCompleted: data => { + hidePassword(data.protectShareToken.hasPassword) + }, + // refetchQueries: [{ query: query, variables: { id } }], + variables: { + token: share.token, + }, + }) let addPasswordInput = null if (showPasswordInput) { - const setPasswordEvent = event => { + const setPasswordEvent = (event: React.KeyboardEvent) => { if (!passwordHidden && passwordInputValue != '' && event.key == 'Enter') { event.preventDefault() setPassword({ variables: { token: share.token, - password: event.target.value, + password: (event.target as HTMLInputElement).value, }, }) } @@ -132,7 +166,7 @@ const ShareItemMoreDropdown = ({ id, share, isPhoto }) => { disabled={setPasswordLoading} loading={setPasswordLoading} style={{ marginTop: 8, marginRight: 0, display: 'block' }} - onClick={e => e.stopPropagation()} + onClick={(e: MouseEvent) => e.stopPropagation()} value={passwordInputValue} type={passwordHidden ? 'password' : 'text'} onKeyUp={setPasswordEvent} @@ -158,6 +192,7 @@ const ShareItemMoreDropdown = ({ id, share, isPhoto }) => { if (!enable) { setPassword({ variables: { + token: share.token, password: null, }, }) @@ -182,7 +217,7 @@ const ShareItemMoreDropdown = ({ id, share, isPhoto }) => { > e.stopPropagation()} + onKeyDown={(e: KeyboardEvent) => e.stopPropagation()} onClick={e => { e.stopPropagation() checkboxClick() @@ -215,88 +250,153 @@ const ShareItemMoreDropdown = ({ id, share, isPhoto }) => { ) } -ShareItemMoreDropdown.propTypes = { - id: PropTypes.string.isRequired, - isPhoto: PropTypes.bool.isRequired, - share: PropTypes.object.isRequired, -} - const ShareButtonGroup = styled(Button.Group)` flex-wrap: wrap; ` -const SidebarShare = ({ photo, album }) => { +type SidebarShareAlbumProps = { + id: string +} + +export const SidebarAlbumShare = ({ id }: SidebarShareAlbumProps) => { const { t } = useTranslation() - if ((!photo || !photo.id) && (!album || !album.id)) return null - if (!authToken()) return null - - const isPhoto = !!photo - const id = isPhoto ? photo.id : album.id - - const query = isPhoto ? sharePhotoQuery : shareAlbumQuery - const addShareMutation = isPhoto - ? addPhotoShareMutation - : addAlbumShareMutation const { - loading: sharesLoading, + loading: queryLoading, error: sharesError, data: sharesData, - } = useQuery(query, { - variables: { id }, + } = useQuery( + SHARE_ALBUM_QUERY, + { variables: { id } } + ) + + const [shareAlbum, { loading: mutationLoading }] = useMutation< + sidebarAlbumAddShare, + sidebarAlbumAddShareVariables + >(ADD_ALBUM_SHARE_MUTATION, { + refetchQueries: [{ query: SHARE_ALBUM_QUERY, variables: { id } }], }) - const [sharePhoto, { loading: sharePhotoLoading }] = useMutation( - addShareMutation, + const loading = queryLoading || mutationLoading + + if (sharesError) { + return
Error: {sharesError.message}
+ } + + if (loading) { + return
{t('general.loading.shares', 'Loading shares...')}
+ } + + return ( + + ) +} +type SidebarSharePhotoProps = { + id: string +} + +export const SidebarPhotoShare = ({ id }: SidebarSharePhotoProps) => { + const { t } = useTranslation() + + const { + loading: queryLoading, + error: sharesError, + data: sharesData, + } = useQuery( + SHARE_PHOTO_QUERY, { - refetchQueries: [{ query: query, variables: { id } }], + variables: { id }, } ) - let content = null + const [sharePhoto, { loading: mutationLoading }] = useMutation< + sidebarPhotoAddShare, + sidebarPhotoAddShareVariables + >(ADD_MEDIA_SHARE_MUTATION, { + refetchQueries: [{ query: SHARE_PHOTO_QUERY, variables: { id } }], + }) + + const loading = queryLoading || mutationLoading if (sharesError) { - content =
Error: {sharesError.message}
+ return
Error: {sharesError.message}
} - if (!content && sharesLoading) { - content =
{t('general.loading.shares', 'Loading shares...')}
+ if (loading) { + return
{t('general.loading.shares', 'Loading shares...')}
} - if (!content) { - const shares = isPhoto ? sharesData.media.shares : sharesData.album.shares + return ( + + ) +} - const optionsRows = shares.map(share => ( - - - {t('sidebar.sharing.public_link', 'Public Link')} {share.token} - - - -