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}
-
-
-
- {
- copy(`${location.origin}/share/${share.token}`)
- }}
- />
-
-
+type SidebarShareProps = {
+ id: string
+ isPhoto: boolean
+ loading: boolean
+ shares?: sidbarGetPhotoShares_media_shares[]
+ shareItem(item: { variables: { id: string } }): void
+}
+
+const SidebarShare = ({
+ loading,
+ shares,
+ isPhoto,
+ id,
+ shareItem,
+}: SidebarShareProps) => {
+ const { t } = useTranslation()
+
+ if (shares === undefined) {
+ return null
+ }
+
+ const optionsRows = shares.map(share => (
+
+
+ {t('sidebar.sharing.public_link', 'Public Link')} {share.token}
+
+
+
+ {
+ copy(`${location.origin}/share/${share.token}`)
+ }}
+ />
+
+
+
+
+ ))
+
+ if (optionsRows.length == 0) {
+ optionsRows.push(
+
+
+ {t('sidebar.sharing.no_shares_found', 'No shares found')}
- ))
+ )
+ }
- if (optionsRows.length == 0) {
- optionsRows.push(
-
-
- {t('sidebar.sharing.no_shares_found', 'No shares found')}
-
-
- )
- }
-
- content = (
+ return (
+
+
{t('sidebar.sharing.title', 'Sharing options')}
@@ -315,10 +415,10 @@ const SidebarShare = ({ photo, album }) => {
icon="add"
floated="right"
positive
- loading={sharePhotoLoading}
- disabled={sharePhotoLoading}
+ loading={loading}
+ disabled={loading}
onClick={() => {
- sharePhoto({
+ shareItem({
variables: {
id,
},
@@ -330,20 +430,6 @@ const SidebarShare = ({ photo, album }) => {
- )
- }
-
- return (
-
-
{t('sidebar.sharing.title', 'Sharing options')}
- {content}
)
}
-
-SidebarShare.propTypes = {
- photo: PropTypes.object,
- album: PropTypes.object,
-}
-
-export default SidebarShare
diff --git a/ui/src/components/sidebar/__generated__/sidebarGetAlbumShares.ts b/ui/src/components/sidebar/__generated__/sidebarGetAlbumShares.ts
new file mode 100644
index 00000000..c7306ded
--- /dev/null
+++ b/ui/src/components/sidebar/__generated__/sidebarGetAlbumShares.ts
@@ -0,0 +1,36 @@
+/* tslint:disable */
+/* eslint-disable */
+// @generated
+// This file was automatically generated and should not be edited.
+
+// ====================================================
+// GraphQL query operation: sidebarGetAlbumShares
+// ====================================================
+
+export interface sidebarGetAlbumShares_album_shares {
+ __typename: 'ShareToken'
+ id: string
+ token: string
+ /**
+ * Whether or not a password is needed to access the share
+ */
+ hasPassword: boolean
+}
+
+export interface sidebarGetAlbumShares_album {
+ __typename: 'Album'
+ id: string
+ shares: sidebarGetAlbumShares_album_shares[]
+}
+
+export interface sidebarGetAlbumShares {
+ /**
+ * Get album by id, user must own the album or be admin
+ * If valid tokenCredentials are provided, the album may be retrived without further authentication
+ */
+ album: sidebarGetAlbumShares_album
+}
+
+export interface sidebarGetAlbumSharesVariables {
+ id: string
+}
diff --git a/ui/src/components/sidebar/__generated__/sidebarGetPhotoShares.ts b/ui/src/components/sidebar/__generated__/sidebarGetPhotoShares.ts
new file mode 100644
index 00000000..edb5f9eb
--- /dev/null
+++ b/ui/src/components/sidebar/__generated__/sidebarGetPhotoShares.ts
@@ -0,0 +1,36 @@
+/* tslint:disable */
+/* eslint-disable */
+// @generated
+// This file was automatically generated and should not be edited.
+
+// ====================================================
+// GraphQL query operation: sidebarGetPhotoShares
+// ====================================================
+
+export interface sidebarGetPhotoShares_media_shares {
+ __typename: 'ShareToken'
+ id: string
+ token: string
+ /**
+ * Whether or not a password is needed to access the share
+ */
+ hasPassword: boolean
+}
+
+export interface sidebarGetPhotoShares_media {
+ __typename: 'Media'
+ id: string
+ shares: sidebarGetPhotoShares_media_shares[]
+}
+
+export interface sidebarGetPhotoShares {
+ /**
+ * Get media by id, user must own the media or be admin.
+ * If valid tokenCredentials are provided, the media may be retrived without further authentication
+ */
+ media: sidebarGetPhotoShares_media
+}
+
+export interface sidebarGetPhotoSharesVariables {
+ id: string
+}