Replace all logs with new logger
This commit is contained in:
parent
e49a3d8a56
commit
f51351e80d
66 changed files with 301 additions and 230 deletions
|
@ -30,6 +30,7 @@ import {usePalette} from 'lib/hooks/usePalette'
|
|||
import {useTheme} from 'lib/ThemeContext'
|
||||
import {cleanError} from 'lib/strings/errors'
|
||||
import {isWeb} from 'platform/detection'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
enum Forms {
|
||||
Login,
|
||||
|
@ -81,10 +82,9 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => {
|
|||
if (aborted) {
|
||||
return
|
||||
}
|
||||
store.log.warn(
|
||||
`Failed to fetch service description for ${serviceUrl}`,
|
||||
{error: err},
|
||||
)
|
||||
logger.warn(`Failed to fetch service description for ${serviceUrl}`, {
|
||||
error: err,
|
||||
})
|
||||
setError(
|
||||
'Unable to contact your service. Please check your Internet connection.',
|
||||
)
|
||||
|
@ -93,7 +93,7 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => {
|
|||
return () => {
|
||||
aborted = true
|
||||
}
|
||||
}, [store.session, store.log, serviceUrl, retryDescribeTrigger])
|
||||
}, [store.session, serviceUrl, retryDescribeTrigger])
|
||||
|
||||
const onPressRetryConnect = () => setRetryDescribeTrigger({})
|
||||
const onPressForgotPassword = () => {
|
||||
|
@ -349,7 +349,7 @@ const LoginForm = ({
|
|||
})
|
||||
} catch (e: any) {
|
||||
const errMsg = e.toString()
|
||||
store.log.warn('Failed to login', {error: e})
|
||||
logger.warn('Failed to login', {error: e})
|
||||
setIsProcessing(false)
|
||||
if (errMsg.includes('Authentication Required')) {
|
||||
setError('Invalid username or password')
|
||||
|
@ -578,7 +578,7 @@ const ForgotPasswordForm = ({
|
|||
onEmailSent()
|
||||
} catch (e: any) {
|
||||
const errMsg = e.toString()
|
||||
store.log.warn('Failed to request password reset', {error: e})
|
||||
logger.warn('Failed to request password reset', {error: e})
|
||||
setIsProcessing(false)
|
||||
if (isNetworkError(e)) {
|
||||
setError(
|
||||
|
@ -694,7 +694,6 @@ const ForgotPasswordForm = ({
|
|||
}
|
||||
|
||||
const SetNewPasswordForm = ({
|
||||
store,
|
||||
error,
|
||||
serviceUrl,
|
||||
setError,
|
||||
|
@ -734,7 +733,7 @@ const SetNewPasswordForm = ({
|
|||
onPasswordSet()
|
||||
} catch (e: any) {
|
||||
const errMsg = e.toString()
|
||||
store.log.warn('Failed to set new password', {error: e})
|
||||
logger.warn('Failed to set new password', {error: e})
|
||||
setIsProcessing(false)
|
||||
if (isNetworkError(e)) {
|
||||
setError(
|
||||
|
|
|
@ -12,6 +12,7 @@ import {useCameraPermission} from 'lib/hooks/usePermissions'
|
|||
import {HITSLOP_10, POST_IMG_MAX} from 'lib/constants'
|
||||
import {GalleryModel} from 'state/models/media/gallery'
|
||||
import {isMobileWeb, isNative} from 'platform/detection'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
type Props = {
|
||||
gallery: GalleryModel
|
||||
|
@ -39,7 +40,7 @@ export function OpenCameraBtn({gallery}: Props) {
|
|||
gallery.add(img)
|
||||
} catch (err: any) {
|
||||
// ignore
|
||||
store.log.warn('Error using camera', {error: err})
|
||||
logger.warn('Error using camera', {error: err})
|
||||
}
|
||||
}, [gallery, track, store, requestCameraAccessIfNeeded])
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
} from 'lib/strings/url-helpers'
|
||||
import {ComposerOpts} from 'state/models/ui/shell'
|
||||
import {POST_IMG_MAX} from 'lib/constants'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
export function useExternalLinkFetch({
|
||||
setQuote,
|
||||
|
@ -46,7 +47,7 @@ export function useExternalLinkFetch({
|
|||
setExtLink(undefined)
|
||||
},
|
||||
err => {
|
||||
store.log.error('Failed to fetch post for quote embedding', {
|
||||
logger.error('Failed to fetch post for quote embedding', {
|
||||
error: err,
|
||||
})
|
||||
setExtLink(undefined)
|
||||
|
@ -66,7 +67,7 @@ export function useExternalLinkFetch({
|
|||
})
|
||||
},
|
||||
err => {
|
||||
store.log.error('Failed to fetch feed for embedding', {error: err})
|
||||
logger.error('Failed to fetch feed for embedding', {error: err})
|
||||
setExtLink(undefined)
|
||||
},
|
||||
)
|
||||
|
@ -84,7 +85,7 @@ export function useExternalLinkFetch({
|
|||
})
|
||||
},
|
||||
err => {
|
||||
store.log.error('Failed to fetch list for embedding', {error: err})
|
||||
logger.error('Failed to fetch list for embedding', {error: err})
|
||||
setExtLink(undefined)
|
||||
},
|
||||
)
|
||||
|
|
|
@ -20,6 +20,7 @@ import {TextLink} from '../util/Link'
|
|||
import {FAB} from '../util/fab/FAB'
|
||||
import {LoadLatestBtn} from '../util/load-latest/LoadLatestBtn'
|
||||
import useAppState from 'react-native-appstate-hook'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
export const FeedPage = observer(function FeedPageImpl({
|
||||
testID,
|
||||
|
@ -66,10 +67,10 @@ export const FeedPage = observer(function FeedPageImpl({
|
|||
if (feed.isLoading) {
|
||||
return
|
||||
}
|
||||
store.log.debug('HomeScreen: Polling for new posts')
|
||||
logger.debug('HomeScreen: Polling for new posts')
|
||||
feed.checkForLatest()
|
||||
},
|
||||
[appState, isScreenFocused, isPageFocused, store, feed],
|
||||
[appState, isScreenFocused, isPageFocused, feed],
|
||||
)
|
||||
|
||||
const scrollToTop = React.useCallback(() => {
|
||||
|
@ -96,7 +97,7 @@ export const FeedPage = observer(function FeedPageImpl({
|
|||
const pollInterval = setInterval(doPoll, POLL_FREQ)
|
||||
|
||||
screen('Feed')
|
||||
store.log.debug('HomeScreen: Updating feed')
|
||||
logger.debug('HomeScreen: Updating feed')
|
||||
feed.checkForLatest()
|
||||
|
||||
return () => {
|
||||
|
|
|
@ -15,6 +15,7 @@ import {pluralize} from 'lib/strings/helpers'
|
|||
import {AtUri} from '@atproto/api'
|
||||
import * as Toast from 'view/com/util/Toast'
|
||||
import {sanitizeHandle} from 'lib/strings/handles'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
export const FeedSourceCard = observer(function FeedSourceCardImpl({
|
||||
item,
|
||||
|
@ -45,7 +46,7 @@ export const FeedSourceCard = observer(function FeedSourceCardImpl({
|
|||
Toast.show('Removed from my feeds')
|
||||
} catch (e) {
|
||||
Toast.show('There was an issue contacting your server')
|
||||
store.log.error('Failed to unsave feed', {error: e})
|
||||
logger.error('Failed to unsave feed', {error: e})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
@ -55,7 +56,7 @@ export const FeedSourceCard = observer(function FeedSourceCardImpl({
|
|||
Toast.show('Added to my feeds')
|
||||
} catch (e) {
|
||||
Toast.show('There was an issue contacting your server')
|
||||
store.log.error('Failed to save feed', {error: e})
|
||||
logger.error('Failed to save feed', {error: e})
|
||||
}
|
||||
}
|
||||
}, [store, item])
|
||||
|
|
|
@ -21,6 +21,7 @@ import {useStores} from 'state/index'
|
|||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||
import {s} from 'lib/styles'
|
||||
import {OnScrollCb} from 'lib/hooks/useOnMainScroll'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
const LOADING_ITEM = {_reactKey: '__loading__'}
|
||||
const EMPTY_ITEM = {_reactKey: '__empty__'}
|
||||
|
@ -94,7 +95,7 @@ export const ListItems = observer(function ListItemsImpl({
|
|||
try {
|
||||
await list.refresh()
|
||||
} catch (err) {
|
||||
list.rootStore.log.error('Failed to refresh lists', {error: err})
|
||||
logger.error('Failed to refresh lists', {error: err})
|
||||
}
|
||||
setIsRefreshing(false)
|
||||
}, [list, track, setIsRefreshing])
|
||||
|
@ -104,7 +105,7 @@ export const ListItems = observer(function ListItemsImpl({
|
|||
try {
|
||||
await list.loadMore()
|
||||
} catch (err) {
|
||||
list.rootStore.log.error('Failed to load more lists', {error: err})
|
||||
logger.error('Failed to load more lists', {error: err})
|
||||
}
|
||||
}, [list, track])
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import {useAnalytics} from 'lib/analytics/analytics'
|
|||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {FlatList} from '../util/Views.web'
|
||||
import {s} from 'lib/styles'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
const LOADING = {_reactKey: '__loading__'}
|
||||
const EMPTY = {_reactKey: '__empty__'}
|
||||
|
@ -78,7 +79,7 @@ export const ListsList = observer(function ListsListImpl({
|
|||
try {
|
||||
await listsList.refresh()
|
||||
} catch (err) {
|
||||
listsList.rootStore.log.error('Failed to refresh lists', {error: err})
|
||||
logger.error('Failed to refresh lists', {error: err})
|
||||
}
|
||||
setIsRefreshing(false)
|
||||
}, [listsList, track, setIsRefreshing])
|
||||
|
@ -88,7 +89,7 @@ export const ListsList = observer(function ListsListImpl({
|
|||
try {
|
||||
await listsList.loadMore()
|
||||
} catch (err) {
|
||||
listsList.rootStore.log.error('Failed to load more lists', {error: err})
|
||||
logger.error('Failed to load more lists', {error: err})
|
||||
}
|
||||
}, [listsList, track])
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
} from '@fortawesome/react-native-fontawesome'
|
||||
import Clipboard from '@react-native-clipboard/clipboard'
|
||||
import * as Toast from '../util/Toast'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
export const snapPoints = ['70%']
|
||||
|
||||
|
@ -95,7 +96,7 @@ export function Component({}: {}) {
|
|||
}
|
||||
} catch (e) {
|
||||
Toast.show('Failed to create app password.')
|
||||
store.log.error('Failed to create app password', {error: e})
|
||||
logger.error('Failed to create app password', {error: e})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import {usePalette} from 'lib/hooks/usePalette'
|
|||
import {useTheme} from 'lib/ThemeContext'
|
||||
import {useAnalytics} from 'lib/analytics/analytics'
|
||||
import {cleanError} from 'lib/strings/errors'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
export const snapPoints = ['100%']
|
||||
|
||||
|
@ -65,7 +66,7 @@ export function Component({onChanged}: {onChanged: () => void}) {
|
|||
return
|
||||
}
|
||||
setProcessing(false)
|
||||
store.log.warn(
|
||||
logger.warn(
|
||||
`Failed to fetch service description for ${String(
|
||||
store.agent.service,
|
||||
)}`,
|
||||
|
@ -79,7 +80,7 @@ export function Component({onChanged}: {onChanged: () => void}) {
|
|||
return () => {
|
||||
aborted = true
|
||||
}
|
||||
}, [store.agent.service, store.session, store.log, retryDescribeTrigger])
|
||||
}, [store.agent.service, store.session, retryDescribeTrigger])
|
||||
|
||||
// events
|
||||
// =
|
||||
|
@ -105,7 +106,7 @@ export function Component({onChanged}: {onChanged: () => void}) {
|
|||
try {
|
||||
track('EditHandle:SetNewHandle')
|
||||
const newHandle = isCustom ? handle : createFullHandle(handle, userDomain)
|
||||
store.log.debug(`Updating handle to ${newHandle}`)
|
||||
logger.debug(`Updating handle to ${newHandle}`)
|
||||
await store.agent.updateHandle({
|
||||
handle: newHandle,
|
||||
})
|
||||
|
@ -113,7 +114,7 @@ export function Component({onChanged}: {onChanged: () => void}) {
|
|||
onChanged()
|
||||
} catch (err: any) {
|
||||
setError(cleanError(err))
|
||||
store.log.error('Failed to update handle', {handle, error: err})
|
||||
logger.error('Failed to update handle', {handle, error: err})
|
||||
} finally {
|
||||
setProcessing(false)
|
||||
}
|
||||
|
@ -343,7 +344,7 @@ function CustomHandleForm({
|
|||
}
|
||||
} catch (err: any) {
|
||||
setError(cleanError(err))
|
||||
store.log.error('Failed to verify domain', {handle, error: err})
|
||||
logger.error('Failed to verify domain', {handle, error: err})
|
||||
} finally {
|
||||
setIsVerifying(false)
|
||||
}
|
||||
|
@ -355,7 +356,6 @@ function CustomHandleForm({
|
|||
setError,
|
||||
canSave,
|
||||
onPressSave,
|
||||
store.log,
|
||||
store.agent,
|
||||
])
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import {CONFIGURABLE_LABEL_GROUPS} from 'lib/labeling/const'
|
|||
import {isIOS} from 'platform/detection'
|
||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||
import * as Toast from '../util/Toast'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
export const snapPoints = ['90%']
|
||||
|
||||
|
@ -103,7 +104,7 @@ const AdultContentEnabledPref = observer(
|
|||
Toast.show(
|
||||
'There was an issue syncing your preferences with the server',
|
||||
)
|
||||
store.log.error('Failed to update preferences with server', {error: e})
|
||||
logger.error('Failed to update preferences with server', {error: e})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -168,7 +169,7 @@ const ContentLabelPref = observer(function ContentLabelPrefImpl({
|
|||
Toast.show(
|
||||
'There was an issue syncing your preferences with the server',
|
||||
)
|
||||
store.log.error('Failed to update preferences with server', {error: e})
|
||||
logger.error('Failed to update preferences with server', {error: e})
|
||||
}
|
||||
},
|
||||
[store, group],
|
||||
|
|
|
@ -20,6 +20,7 @@ import {s} from 'lib/styles'
|
|||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {isWeb, isAndroid} from 'platform/detection'
|
||||
import isEqual from 'lodash.isequal'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
export const snapPoints = ['fullscreen']
|
||||
|
||||
|
@ -62,7 +63,7 @@ export const Component = observer(function UserAddRemoveListsImpl({
|
|||
setMembershipsLoaded(true)
|
||||
},
|
||||
err => {
|
||||
store.log.error('Failed to fetch memberships', {error: err})
|
||||
logger.error('Failed to fetch memberships', {error: err})
|
||||
},
|
||||
)
|
||||
}, [memberships, listsList, store, setSelected, setMembershipsLoaded])
|
||||
|
@ -76,7 +77,7 @@ export const Component = observer(function UserAddRemoveListsImpl({
|
|||
try {
|
||||
changes = await memberships.updateTo(selected)
|
||||
} catch (err) {
|
||||
store.log.error('Failed to update memberships', {error: err})
|
||||
logger.error('Failed to update memberships', {error: err})
|
||||
return
|
||||
}
|
||||
Toast.show('Lists updated')
|
||||
|
|
|
@ -11,6 +11,7 @@ import {EmptyState} from '../util/EmptyState'
|
|||
import {OnScrollCb} from 'lib/hooks/useOnMainScroll'
|
||||
import {s} from 'lib/styles'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
|
||||
const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'}
|
||||
|
@ -61,7 +62,7 @@ export const Feed = observer(function Feed({
|
|||
setIsPTRing(true)
|
||||
await view.refresh()
|
||||
} catch (err) {
|
||||
view.rootStore.log.error('Failed to refresh notifications feed', {
|
||||
logger.error('Failed to refresh notifications feed', {
|
||||
error: err,
|
||||
})
|
||||
} finally {
|
||||
|
@ -73,7 +74,7 @@ export const Feed = observer(function Feed({
|
|||
try {
|
||||
await view.loadMore()
|
||||
} catch (err) {
|
||||
view.rootStore.log.error('Failed to load more notifications', {
|
||||
logger.error('Failed to load more notifications', {
|
||||
error: err,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import {ErrorMessage} from '../util/error/ErrorMessage'
|
|||
import {ProfileCardWithFollowBtn} from '../profile/ProfileCard'
|
||||
import {useStores} from 'state/index'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
export const PostLikedBy = observer(function PostLikedByImpl({
|
||||
uri,
|
||||
|
@ -20,8 +21,8 @@ export const PostLikedBy = observer(function PostLikedByImpl({
|
|||
useEffect(() => {
|
||||
view
|
||||
.loadMore()
|
||||
.catch(err => store.log.error('Failed to fetch likes', {error: err}))
|
||||
}, [view, store.log])
|
||||
.catch(err => logger.error('Failed to fetch likes', {error: err}))
|
||||
}, [view])
|
||||
|
||||
const onRefresh = () => {
|
||||
view.refresh()
|
||||
|
@ -29,9 +30,7 @@ export const PostLikedBy = observer(function PostLikedByImpl({
|
|||
const onEndReached = () => {
|
||||
view
|
||||
.loadMore()
|
||||
.catch(err =>
|
||||
view?.rootStore.log.error('Failed to load more likes', {error: err}),
|
||||
)
|
||||
.catch(err => logger.error('Failed to load more likes', {error: err}))
|
||||
}
|
||||
|
||||
if (!view.hasLoaded) {
|
||||
|
|
|
@ -7,6 +7,7 @@ import {ProfileCardWithFollowBtn} from '../profile/ProfileCard'
|
|||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||
import {useStores} from 'state/index'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
export const PostRepostedBy = observer(function PostRepostedByImpl({
|
||||
uri,
|
||||
|
@ -23,8 +24,8 @@ export const PostRepostedBy = observer(function PostRepostedByImpl({
|
|||
useEffect(() => {
|
||||
view
|
||||
.loadMore()
|
||||
.catch(err => store.log.error('Failed to fetch reposts', {error: err}))
|
||||
}, [view, store.log])
|
||||
.catch(err => logger.error('Failed to fetch reposts', {error: err}))
|
||||
}, [view])
|
||||
|
||||
const onRefresh = () => {
|
||||
view.refresh()
|
||||
|
@ -32,9 +33,7 @@ export const PostRepostedBy = observer(function PostRepostedByImpl({
|
|||
const onEndReached = () => {
|
||||
view
|
||||
.loadMore()
|
||||
.catch(err =>
|
||||
view?.rootStore.log.error('Failed to load more reposts', {error: err}),
|
||||
)
|
||||
.catch(err => logger.error('Failed to load more reposts', {error: err}))
|
||||
}
|
||||
|
||||
if (!view.hasLoaded) {
|
||||
|
|
|
@ -30,6 +30,7 @@ import {useNavigation} from '@react-navigation/native'
|
|||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||
import {NavigationProp} from 'lib/routes/types'
|
||||
import {sanitizeDisplayName} from 'lib/strings/display-names'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
const MAINTAIN_VISIBLE_CONTENT_POSITION = {minIndexForVisible: 2}
|
||||
|
||||
|
@ -119,7 +120,7 @@ export const PostThread = observer(function PostThread({
|
|||
try {
|
||||
view?.refresh()
|
||||
} catch (err) {
|
||||
view.rootStore.log.error('Failed to refresh posts thread', {error: err})
|
||||
logger.error('Failed to refresh posts thread', {error: err})
|
||||
}
|
||||
setIsRefreshing(false)
|
||||
}, [view, setIsRefreshing])
|
||||
|
|
|
@ -36,6 +36,7 @@ import {TimeElapsed} from 'view/com/util/TimeElapsed'
|
|||
import {makeProfileLink} from 'lib/routes/links'
|
||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||
import {MAX_POST_LINES} from 'lib/constants'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
export const PostThreadItem = observer(function PostThreadItem({
|
||||
item,
|
||||
|
@ -111,14 +112,14 @@ export const PostThreadItem = observer(function PostThreadItem({
|
|||
const onPressToggleRepost = React.useCallback(() => {
|
||||
return item
|
||||
.toggleRepost()
|
||||
.catch(e => store.log.error('Failed to toggle repost', {error: e}))
|
||||
}, [item, store])
|
||||
.catch(e => logger.error('Failed to toggle repost', {error: e}))
|
||||
}, [item])
|
||||
|
||||
const onPressToggleLike = React.useCallback(() => {
|
||||
return item
|
||||
.toggleLike()
|
||||
.catch(e => store.log.error('Failed to toggle like', {error: e}))
|
||||
}, [item, store])
|
||||
.catch(e => logger.error('Failed to toggle like', {error: e}))
|
||||
}, [item])
|
||||
|
||||
const onCopyPostText = React.useCallback(() => {
|
||||
Clipboard.setString(record?.text || '')
|
||||
|
@ -138,9 +139,9 @@ export const PostThreadItem = observer(function PostThreadItem({
|
|||
Toast.show('You will now receive notifications for this thread')
|
||||
}
|
||||
} catch (e) {
|
||||
store.log.error('Failed to toggle thread mute', {error: e})
|
||||
logger.error('Failed to toggle thread mute', {error: e})
|
||||
}
|
||||
}, [item, store])
|
||||
}, [item])
|
||||
|
||||
const onDeletePost = React.useCallback(() => {
|
||||
item.delete().then(
|
||||
|
@ -149,11 +150,11 @@ export const PostThreadItem = observer(function PostThreadItem({
|
|||
Toast.show('Post deleted')
|
||||
},
|
||||
e => {
|
||||
store.log.error('Failed to delete post', {error: e})
|
||||
logger.error('Failed to delete post', {error: e})
|
||||
Toast.show('Failed to delete post, please try again')
|
||||
},
|
||||
)
|
||||
}, [item, store])
|
||||
}, [item])
|
||||
|
||||
const onPressShowMore = React.useCallback(() => {
|
||||
setLimitLines(false)
|
||||
|
|
|
@ -32,6 +32,7 @@ import {getTranslatorLink} from '../../../locale/helpers'
|
|||
import {makeProfileLink} from 'lib/routes/links'
|
||||
import {MAX_POST_LINES} from 'lib/constants'
|
||||
import {countLines} from 'lib/strings/helpers'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
export const Post = observer(function PostImpl({
|
||||
view,
|
||||
|
@ -142,14 +143,14 @@ const PostLoaded = observer(function PostLoadedImpl({
|
|||
const onPressToggleRepost = React.useCallback(() => {
|
||||
return item
|
||||
.toggleRepost()
|
||||
.catch(e => store.log.error('Failed to toggle repost', {error: e}))
|
||||
}, [item, store])
|
||||
.catch(e => logger.error('Failed to toggle repost', {error: e}))
|
||||
}, [item])
|
||||
|
||||
const onPressToggleLike = React.useCallback(() => {
|
||||
return item
|
||||
.toggleLike()
|
||||
.catch(e => store.log.error('Failed to toggle like', {error: e}))
|
||||
}, [item, store])
|
||||
.catch(e => logger.error('Failed to toggle like', {error: e}))
|
||||
}, [item])
|
||||
|
||||
const onCopyPostText = React.useCallback(() => {
|
||||
Clipboard.setString(record.text)
|
||||
|
@ -169,9 +170,9 @@ const PostLoaded = observer(function PostLoadedImpl({
|
|||
Toast.show('You will now receive notifications for this thread')
|
||||
}
|
||||
} catch (e) {
|
||||
store.log.error('Failed to toggle thread mute', {error: e})
|
||||
logger.error('Failed to toggle thread mute', {error: e})
|
||||
}
|
||||
}, [item, store])
|
||||
}, [item])
|
||||
|
||||
const onDeletePost = React.useCallback(() => {
|
||||
item.delete().then(
|
||||
|
@ -180,11 +181,11 @@ const PostLoaded = observer(function PostLoadedImpl({
|
|||
Toast.show('Post deleted')
|
||||
},
|
||||
e => {
|
||||
store.log.error('Failed to delete post', {error: e})
|
||||
logger.error('Failed to delete post', {error: e})
|
||||
Toast.show('Failed to delete post, please try again')
|
||||
},
|
||||
)
|
||||
}, [item, setDeleted, store])
|
||||
}, [item, setDeleted])
|
||||
|
||||
const onPressShowMore = React.useCallback(() => {
|
||||
setLimitLines(false)
|
||||
|
|
|
@ -19,6 +19,7 @@ import {s} from 'lib/styles'
|
|||
import {useAnalytics} from 'lib/analytics/analytics'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {useTheme} from 'lib/ThemeContext'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
const LOADING_ITEM = {_reactKey: '__loading__'}
|
||||
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
|
||||
|
@ -92,7 +93,7 @@ export const Feed = observer(function Feed({
|
|||
try {
|
||||
await feed.refresh()
|
||||
} catch (err) {
|
||||
feed.rootStore.log.error('Failed to refresh posts feed', {error: err})
|
||||
logger.error('Failed to refresh posts feed', {error: err})
|
||||
}
|
||||
setIsRefreshing(false)
|
||||
}, [feed, track, setIsRefreshing])
|
||||
|
@ -104,7 +105,7 @@ export const Feed = observer(function Feed({
|
|||
try {
|
||||
await feed.loadMore()
|
||||
} catch (err) {
|
||||
feed.rootStore.log.error('Failed to load more posts', {error: err})
|
||||
logger.error('Failed to load more posts', {error: err})
|
||||
}
|
||||
}, [feed, track])
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import {usePalette} from 'lib/hooks/usePalette'
|
|||
import {useNavigation} from '@react-navigation/native'
|
||||
import {NavigationProp} from 'lib/routes/types'
|
||||
import {useStores} from 'state/index'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
const MESSAGES = {
|
||||
[KnownError.Unknown]: '',
|
||||
|
@ -73,7 +74,7 @@ function FeedgenErrorMessage({
|
|||
Toast.show(
|
||||
'There was an an issue removing this feed. Please check your internet connection and try again.',
|
||||
)
|
||||
store.log.error('Failed to remove feed', {error: err})
|
||||
logger.error('Failed to remove feed', {error: err})
|
||||
}
|
||||
},
|
||||
onPressCancel() {
|
||||
|
|
|
@ -32,6 +32,7 @@ import {makeProfileLink} from 'lib/routes/links'
|
|||
import {isEmbedByEmbedder} from 'lib/embeds'
|
||||
import {MAX_POST_LINES} from 'lib/constants'
|
||||
import {countLines} from 'lib/strings/helpers'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
export const FeedItem = observer(function FeedItemImpl({
|
||||
item,
|
||||
|
@ -94,15 +95,15 @@ export const FeedItem = observer(function FeedItemImpl({
|
|||
track('FeedItem:PostRepost')
|
||||
return item
|
||||
.toggleRepost()
|
||||
.catch(e => store.log.error('Failed to toggle repost', {error: e}))
|
||||
}, [track, item, store])
|
||||
.catch(e => logger.error('Failed to toggle repost', {error: e}))
|
||||
}, [track, item])
|
||||
|
||||
const onPressToggleLike = React.useCallback(() => {
|
||||
track('FeedItem:PostLike')
|
||||
return item
|
||||
.toggleLike()
|
||||
.catch(e => store.log.error('Failed to toggle like', {error: e}))
|
||||
}, [track, item, store])
|
||||
.catch(e => logger.error('Failed to toggle like', {error: e}))
|
||||
}, [track, item])
|
||||
|
||||
const onCopyPostText = React.useCallback(() => {
|
||||
Clipboard.setString(record?.text || '')
|
||||
|
@ -123,9 +124,9 @@ export const FeedItem = observer(function FeedItemImpl({
|
|||
Toast.show('You will now receive notifications for this thread')
|
||||
}
|
||||
} catch (e) {
|
||||
store.log.error('Failed to toggle thread mute', {error: e})
|
||||
logger.error('Failed to toggle thread mute', {error: e})
|
||||
}
|
||||
}, [track, item, store])
|
||||
}, [track, item])
|
||||
|
||||
const onDeletePost = React.useCallback(() => {
|
||||
track('FeedItem:PostDelete')
|
||||
|
@ -135,11 +136,11 @@ export const FeedItem = observer(function FeedItemImpl({
|
|||
Toast.show('Post deleted')
|
||||
},
|
||||
e => {
|
||||
store.log.error('Failed to delete post', {error: e})
|
||||
logger.error('Failed to delete post', {error: e})
|
||||
Toast.show('Failed to delete post, please try again')
|
||||
},
|
||||
)
|
||||
}, [track, item, setDeleted, store])
|
||||
}, [track, item, setDeleted])
|
||||
|
||||
const onPressShowMore = React.useCallback(() => {
|
||||
setLimitLines(false)
|
||||
|
|
|
@ -10,6 +10,7 @@ import {ErrorMessage} from '../util/error/ErrorMessage'
|
|||
import {ProfileCardWithFollowBtn} from './ProfileCard'
|
||||
import {useStores} from 'state/index'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
export const ProfileFollowers = observer(function ProfileFollowers({
|
||||
name,
|
||||
|
@ -27,16 +28,16 @@ export const ProfileFollowers = observer(function ProfileFollowers({
|
|||
view
|
||||
.loadMore()
|
||||
.catch(err =>
|
||||
store.log.error('Failed to fetch user followers', {error: err}),
|
||||
logger.error('Failed to fetch user followers', {error: err}),
|
||||
)
|
||||
}, [view, store.log])
|
||||
}, [view])
|
||||
|
||||
const onRefresh = () => {
|
||||
view.refresh()
|
||||
}
|
||||
const onEndReached = () => {
|
||||
view.loadMore().catch(err =>
|
||||
view?.rootStore.log.error('Failed to load more followers', {
|
||||
logger.error('Failed to load more followers', {
|
||||
error: err,
|
||||
}),
|
||||
)
|
||||
|
|
|
@ -7,6 +7,7 @@ import {ErrorMessage} from '../util/error/ErrorMessage'
|
|||
import {ProfileCardWithFollowBtn} from './ProfileCard'
|
||||
import {useStores} from 'state/index'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
export const ProfileFollows = observer(function ProfileFollows({
|
||||
name,
|
||||
|
@ -23,8 +24,8 @@ export const ProfileFollows = observer(function ProfileFollows({
|
|||
useEffect(() => {
|
||||
view
|
||||
.loadMore()
|
||||
.catch(err => store.log.error('Failed to fetch user follows', err))
|
||||
}, [view, store.log])
|
||||
.catch(err => logger.error('Failed to fetch user follows', err))
|
||||
}, [view])
|
||||
|
||||
const onRefresh = () => {
|
||||
view.refresh()
|
||||
|
@ -32,9 +33,7 @@ export const ProfileFollows = observer(function ProfileFollows({
|
|||
const onEndReached = () => {
|
||||
view
|
||||
.loadMore()
|
||||
.catch(err =>
|
||||
view?.rootStore.log.error('Failed to load more follows', err),
|
||||
)
|
||||
.catch(err => logger.error('Failed to load more follows', err))
|
||||
}
|
||||
|
||||
if (!view.hasLoaded) {
|
||||
|
|
|
@ -39,6 +39,7 @@ import {isInvalidHandle} from 'lib/strings/handles'
|
|||
import {makeProfileLink} from 'lib/routes/links'
|
||||
import {Link} from '../util/Link'
|
||||
import {ProfileHeaderSuggestedFollows} from './ProfileHeaderSuggestedFollows'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
interface Props {
|
||||
view: ProfileModel
|
||||
|
@ -150,9 +151,9 @@ const ProfileHeaderLoaded = observer(function ProfileHeaderLoadedImpl({
|
|||
: 'ProfileHeader:UnfollowButtonClicked',
|
||||
)
|
||||
},
|
||||
err => store.log.error('Failed to toggle follow', {error: err}),
|
||||
err => logger.error('Failed to toggle follow', {error: err}),
|
||||
)
|
||||
}, [track, view, store.log, setShowSuggestedFollows])
|
||||
}, [track, view, setShowSuggestedFollows])
|
||||
|
||||
const onPressEditProfile = React.useCallback(() => {
|
||||
track('ProfileHeader:EditProfileButtonClicked')
|
||||
|
@ -193,10 +194,10 @@ const ProfileHeaderLoaded = observer(function ProfileHeaderLoadedImpl({
|
|||
await view.muteAccount()
|
||||
Toast.show('Account muted')
|
||||
} catch (e: any) {
|
||||
store.log.error('Failed to mute account', {error: e})
|
||||
logger.error('Failed to mute account', {error: e})
|
||||
Toast.show(`There was an issue! ${e.toString()}`)
|
||||
}
|
||||
}, [track, view, store])
|
||||
}, [track, view])
|
||||
|
||||
const onPressUnmuteAccount = React.useCallback(async () => {
|
||||
track('ProfileHeader:UnmuteAccountButtonClicked')
|
||||
|
@ -204,10 +205,10 @@ const ProfileHeaderLoaded = observer(function ProfileHeaderLoadedImpl({
|
|||
await view.unmuteAccount()
|
||||
Toast.show('Account unmuted')
|
||||
} catch (e: any) {
|
||||
store.log.error('Failed to unmute account', {error: e})
|
||||
logger.error('Failed to unmute account', {error: e})
|
||||
Toast.show(`There was an issue! ${e.toString()}`)
|
||||
}
|
||||
}, [track, view, store])
|
||||
}, [track, view])
|
||||
|
||||
const onPressBlockAccount = React.useCallback(async () => {
|
||||
track('ProfileHeader:BlockAccountButtonClicked')
|
||||
|
@ -222,7 +223,7 @@ const ProfileHeaderLoaded = observer(function ProfileHeaderLoadedImpl({
|
|||
onRefreshAll()
|
||||
Toast.show('Account blocked')
|
||||
} catch (e: any) {
|
||||
store.log.error('Failed to block account', {error: e})
|
||||
logger.error('Failed to block account', {error: e})
|
||||
Toast.show(`There was an issue! ${e.toString()}`)
|
||||
}
|
||||
},
|
||||
|
@ -242,7 +243,7 @@ const ProfileHeaderLoaded = observer(function ProfileHeaderLoadedImpl({
|
|||
onRefreshAll()
|
||||
Toast.show('Account unblocked')
|
||||
} catch (e: any) {
|
||||
store.log.error('Failed to unblock account', {error: e})
|
||||
logger.error('Failed to unblock account', {error: e})
|
||||
Toast.show(`There was an issue! ${e.toString()}`)
|
||||
}
|
||||
},
|
||||
|
|
|
@ -21,6 +21,7 @@ import {useFocusEffect} from '@react-navigation/native'
|
|||
import {ViewHeader} from '../com/util/ViewHeader'
|
||||
import {CenteredView} from 'view/com/util/Views'
|
||||
import {ProfileCard} from 'view/com/profile/ProfileCard'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
type Props = NativeStackScreenProps<
|
||||
CommonNavigatorParams,
|
||||
|
@ -52,9 +53,9 @@ export const ModerationBlockedAccounts = withAuthRequired(
|
|||
blockedAccounts
|
||||
.loadMore()
|
||||
.catch(err =>
|
||||
store.log.error('Failed to load more blocked accounts', {error: err}),
|
||||
logger.error('Failed to load more blocked accounts', {error: err}),
|
||||
)
|
||||
}, [blockedAccounts, store])
|
||||
}, [blockedAccounts])
|
||||
|
||||
const renderItem = ({
|
||||
item,
|
||||
|
|
|
@ -21,6 +21,7 @@ import {useFocusEffect} from '@react-navigation/native'
|
|||
import {ViewHeader} from '../com/util/ViewHeader'
|
||||
import {CenteredView} from 'view/com/util/Views'
|
||||
import {ProfileCard} from 'view/com/profile/ProfileCard'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
type Props = NativeStackScreenProps<
|
||||
CommonNavigatorParams,
|
||||
|
@ -49,9 +50,9 @@ export const ModerationMutedAccounts = withAuthRequired(
|
|||
mutedAccounts
|
||||
.loadMore()
|
||||
.catch(err =>
|
||||
store.log.error('Failed to load more muted accounts', {error: err}),
|
||||
logger.error('Failed to load more muted accounts', {error: err}),
|
||||
)
|
||||
}, [mutedAccounts, store])
|
||||
}, [mutedAccounts])
|
||||
|
||||
const renderItem = ({
|
||||
item,
|
||||
|
|
|
@ -20,6 +20,7 @@ import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
|||
import {s, colors} from 'lib/styles'
|
||||
import {useAnalytics} from 'lib/analytics/analytics'
|
||||
import {isWeb} from 'platform/detection'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
type Props = NativeStackScreenProps<
|
||||
NotificationsTabNavigatorParams,
|
||||
|
@ -60,7 +61,7 @@ export const NotificationsScreen = withAuthRequired(
|
|||
useFocusEffect(
|
||||
React.useCallback(() => {
|
||||
store.shell.setMinimalShellMode(false)
|
||||
store.log.debug('NotificationsScreen: Updating feed')
|
||||
logger.debug('NotificationsScreen: Updating feed')
|
||||
const softResetSub = store.onScreenSoftReset(onPressLoadLatest)
|
||||
store.me.notifications.update()
|
||||
screen('Notifications')
|
||||
|
|
|
@ -14,6 +14,7 @@ import {s} from 'lib/styles'
|
|||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||
import {clamp} from 'lodash'
|
||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
const SHELL_FOOTER_HEIGHT = 44
|
||||
|
||||
|
@ -38,7 +39,7 @@ export const PostThreadScreen = withAuthRequired(
|
|||
InteractionManager.runAfterInteractions(() => {
|
||||
if (!view.hasLoaded && !view.isLoading) {
|
||||
view.setup().catch(err => {
|
||||
store.log.error('Failed to fetch thread', {error: err})
|
||||
logger.error('Failed to fetch thread', {error: err})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -29,6 +29,7 @@ import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard'
|
|||
import {FeedSourceModel} from 'state/models/content/feed-source'
|
||||
import {useSetTitle} from 'lib/hooks/useSetTitle'
|
||||
import {combinedDisplayName} from 'lib/strings/display-names'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Profile'>
|
||||
export const ProfileScreen = withAuthRequired(
|
||||
|
@ -108,16 +109,16 @@ export const ProfileScreen = withAuthRequired(
|
|||
uiState
|
||||
.refresh()
|
||||
.catch((err: any) =>
|
||||
store.log.error('Failed to refresh user profile', {error: err}),
|
||||
logger.error('Failed to refresh user profile', {error: err}),
|
||||
)
|
||||
}, [uiState, store])
|
||||
}, [uiState])
|
||||
const onEndReached = React.useCallback(() => {
|
||||
uiState.loadMore().catch((err: any) =>
|
||||
store.log.error('Failed to load more entries in user profile', {
|
||||
logger.error('Failed to load more entries in user profile', {
|
||||
error: err,
|
||||
}),
|
||||
)
|
||||
}, [uiState, store])
|
||||
}, [uiState])
|
||||
const onPressTryAgain = React.useCallback(() => {
|
||||
uiState.setup()
|
||||
}, [uiState])
|
||||
|
|
|
@ -40,6 +40,7 @@ import {NavigationProp} from 'lib/routes/types'
|
|||
import {sanitizeHandle} from 'lib/strings/handles'
|
||||
import {makeProfileLink} from 'lib/routes/links'
|
||||
import {ComposeIcon2} from 'lib/icons'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
const SECTION_TITLES = ['Posts', 'About']
|
||||
|
||||
|
@ -165,9 +166,9 @@ export const ProfileFeedScreenInner = observer(
|
|||
Toast.show(
|
||||
'There was an an issue updating your feeds, please check your internet connection and try again.',
|
||||
)
|
||||
store.log.error('Failed up update feeds', {error: err})
|
||||
logger.error('Failed up update feeds', {error: err})
|
||||
}
|
||||
}, [store, feedInfo])
|
||||
}, [feedInfo])
|
||||
|
||||
const onToggleLiked = React.useCallback(async () => {
|
||||
Haptics.default()
|
||||
|
@ -181,19 +182,19 @@ export const ProfileFeedScreenInner = observer(
|
|||
Toast.show(
|
||||
'There was an an issue contacting the server, please check your internet connection and try again.',
|
||||
)
|
||||
store.log.error('Failed up toggle like', {error: err})
|
||||
logger.error('Failed up toggle like', {error: err})
|
||||
}
|
||||
}, [store, feedInfo])
|
||||
}, [feedInfo])
|
||||
|
||||
const onTogglePinned = React.useCallback(async () => {
|
||||
Haptics.default()
|
||||
if (feedInfo) {
|
||||
feedInfo.togglePin().catch(e => {
|
||||
Toast.show('There was an issue contacting the server')
|
||||
store.log.error('Failed to toggle pinned feed', {error: e})
|
||||
logger.error('Failed to toggle pinned feed', {error: e})
|
||||
})
|
||||
}
|
||||
}, [store, feedInfo])
|
||||
}, [feedInfo])
|
||||
|
||||
const onPressShare = React.useCallback(() => {
|
||||
const url = toShareUrl(`/profile/${handleOrDid}/feed/${rkey}`)
|
||||
|
|
|
@ -43,6 +43,7 @@ import {sanitizeHandle} from 'lib/strings/handles'
|
|||
import {makeProfileLink, makeListLink} from 'lib/routes/links'
|
||||
import {ComposeIcon2} from 'lib/icons'
|
||||
import {ListItems} from 'view/com/lists/ListItems'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
const SECTION_TITLES_CURATE = ['Posts', 'About']
|
||||
const SECTION_TITLES_MOD = ['About']
|
||||
|
@ -272,9 +273,9 @@ const Header = observer(function HeaderImpl({
|
|||
Haptics.default()
|
||||
list.togglePin().catch(e => {
|
||||
Toast.show('There was an issue contacting the server')
|
||||
store.log.error('Failed to toggle pinned list', {error: e})
|
||||
logger.error('Failed to toggle pinned list', {error: e})
|
||||
})
|
||||
}, [store, list])
|
||||
}, [list])
|
||||
|
||||
const onSubscribeMute = useCallback(() => {
|
||||
store.shell.openModal({
|
||||
|
|
|
@ -26,6 +26,7 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
|||
import * as Toast from 'view/com/util/Toast'
|
||||
import {Haptics} from 'lib/haptics'
|
||||
import {TextLink} from 'view/com/util/Link'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
const HITSLOP_TOP = {
|
||||
top: 20,
|
||||
|
@ -159,31 +160,30 @@ const ListItem = observer(function ListItemImpl({
|
|||
item: FeedSourceModel
|
||||
}) {
|
||||
const pal = usePalette('default')
|
||||
const store = useStores()
|
||||
const isPinned = item.isPinned
|
||||
|
||||
const onTogglePinned = useCallback(() => {
|
||||
Haptics.default()
|
||||
item.togglePin().catch(e => {
|
||||
Toast.show('There was an issue contacting the server')
|
||||
store.log.error('Failed to toggle pinned feed', {error: e})
|
||||
logger.error('Failed to toggle pinned feed', {error: e})
|
||||
})
|
||||
}, [item, store])
|
||||
}, [item])
|
||||
const onPressUp = useCallback(
|
||||
() =>
|
||||
savedFeeds.movePinnedFeed(item, 'up').catch(e => {
|
||||
Toast.show('There was an issue contacting the server')
|
||||
store.log.error('Failed to set pinned feed order', {error: e})
|
||||
logger.error('Failed to set pinned feed order', {error: e})
|
||||
}),
|
||||
[store, savedFeeds, item],
|
||||
[savedFeeds, item],
|
||||
)
|
||||
const onPressDown = useCallback(
|
||||
() =>
|
||||
savedFeeds.movePinnedFeed(item, 'down').catch(e => {
|
||||
Toast.show('There was an issue contacting the server')
|
||||
store.log.error('Failed to set pinned feed order', {error: e})
|
||||
logger.error('Failed to set pinned feed order', {error: e})
|
||||
}),
|
||||
[store, savedFeeds, item],
|
||||
[savedFeeds, item],
|
||||
)
|
||||
|
||||
return (
|
||||
|
|
|
@ -45,6 +45,7 @@ import {formatCount} from 'view/com/util/numeric/format'
|
|||
import Clipboard from '@react-native-clipboard/clipboard'
|
||||
import {makeProfileLink} from 'lib/routes/links'
|
||||
import {AccountDropdownBtn} from 'view/com/util/AccountDropdownBtn'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
// TEMPORARY (APP-700)
|
||||
// remove after backend testing finishes
|
||||
|
@ -110,10 +111,9 @@ export const SettingsScreen = withAuthRequired(
|
|||
Toast.show('Your handle has been updated')
|
||||
},
|
||||
err => {
|
||||
store.log.error(
|
||||
'Failed to reload from server after handle update',
|
||||
{error: err},
|
||||
)
|
||||
logger.error('Failed to reload from server after handle update', {
|
||||
error: err,
|
||||
})
|
||||
setIsSwitching(false)
|
||||
},
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue