Merge branch 'ts' into 'develop'
JS -> TS, FC See merge request soapbox-pub/soapbox!1634
This commit is contained in:
commit
36acb77175
16 changed files with 179 additions and 22 deletions
|
@ -1,8 +1,10 @@
|
|||
import type { ModalType } from 'soapbox/features/ui/components/modal_root';
|
||||
|
||||
export const MODAL_OPEN = 'MODAL_OPEN';
|
||||
export const MODAL_CLOSE = 'MODAL_CLOSE';
|
||||
|
||||
/** Open a modal of the given type */
|
||||
export function openModal(type: string, props?: any) {
|
||||
export function openModal(type: ModalType, props?: any) {
|
||||
return {
|
||||
type: MODAL_OPEN,
|
||||
modalType: type,
|
||||
|
@ -11,7 +13,7 @@ export function openModal(type: string, props?: any) {
|
|||
}
|
||||
|
||||
/** Close the modal */
|
||||
export function closeModal(type?: string) {
|
||||
export function closeModal(type?: ModalType) {
|
||||
return {
|
||||
type: MODAL_CLOSE,
|
||||
modalType: type,
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
import {
|
||||
Map as ImmutableMap,
|
||||
} from 'immutable';
|
||||
import IntlMessageFormat from 'intl-messageformat';
|
||||
import 'intl-pluralrules';
|
||||
import { defineMessages } from 'react-intl';
|
||||
|
@ -149,13 +146,13 @@ const updateNotificationsQueue = (notification: APIEntity, intlMessages: Record<
|
|||
|
||||
const dequeueNotifications = () =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const queuedNotifications = getState().notifications.get('queuedNotifications');
|
||||
const totalQueuedNotificationsCount = getState().notifications.get('totalQueuedNotificationsCount');
|
||||
const queuedNotifications = getState().notifications.queuedNotifications;
|
||||
const totalQueuedNotificationsCount = getState().notifications.totalQueuedNotificationsCount;
|
||||
|
||||
if (totalQueuedNotificationsCount === 0) {
|
||||
return;
|
||||
} else if (totalQueuedNotificationsCount > 0 && totalQueuedNotificationsCount <= MAX_QUEUED_NOTIFICATIONS) {
|
||||
queuedNotifications.forEach((block: APIEntity) => {
|
||||
queuedNotifications.forEach((block) => {
|
||||
dispatch(updateNotifications(block.notification));
|
||||
});
|
||||
} else {
|
||||
|
@ -184,7 +181,7 @@ const expandNotifications = ({ maxId }: Record<string, any> = {}, done: () => an
|
|||
const notifications = state.notifications;
|
||||
const isLoadingMore = !!maxId;
|
||||
|
||||
if (notifications.get('isLoading')) {
|
||||
if (notifications.isLoading) {
|
||||
done();
|
||||
return dispatch(noOp);
|
||||
}
|
||||
|
@ -207,7 +204,7 @@ const expandNotifications = ({ maxId }: Record<string, any> = {}, done: () => an
|
|||
}
|
||||
}
|
||||
|
||||
if (!maxId && notifications.get('items').size > 0) {
|
||||
if (!maxId && notifications.items.size > 0) {
|
||||
params.since_id = notifications.getIn(['items', 0, 'id']);
|
||||
}
|
||||
|
||||
|
@ -306,8 +303,8 @@ const markReadNotifications = () =>
|
|||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
const state = getState();
|
||||
const topNotificationId: string | undefined = state.notifications.get('items').first(ImmutableMap()).get('id');
|
||||
const lastReadId: string | -1 = state.notifications.get('lastRead');
|
||||
const topNotificationId = state.notifications.items.first()?.id;
|
||||
const lastReadId = state.notifications.lastRead;
|
||||
const v = parseVersion(state.instance.version);
|
||||
|
||||
if (topNotificationId && (lastReadId === -1 || compareId(topNotificationId, lastReadId) > 0)) {
|
||||
|
|
Binary file not shown.
64
app/soapbox/components/extended_video_player.tsx
Normal file
64
app/soapbox/components/extended_video_player.tsx
Normal file
|
@ -0,0 +1,64 @@
|
|||
import React, { useEffect, useRef } from 'react';
|
||||
|
||||
import { isIOS } from 'soapbox/is_mobile';
|
||||
|
||||
interface IExtendedVideoPlayer {
|
||||
src: string,
|
||||
alt?: string,
|
||||
width?: number,
|
||||
height?: number,
|
||||
time?: number,
|
||||
controls?: boolean,
|
||||
muted?: boolean,
|
||||
onClick?: () => void,
|
||||
}
|
||||
|
||||
const ExtendedVideoPlayer: React.FC<IExtendedVideoPlayer> = ({ src, alt, time, controls, muted, onClick }) => {
|
||||
const video = useRef<HTMLVideoElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleLoadedData = () => {
|
||||
if (time) {
|
||||
video.current!.currentTime = time;
|
||||
}
|
||||
};
|
||||
|
||||
video.current?.addEventListener('loadeddata', handleLoadedData);
|
||||
|
||||
return () => {
|
||||
video.current?.removeEventListener('loadeddata', handleLoadedData);
|
||||
};
|
||||
}, [video.current]);
|
||||
|
||||
const handleClick: React.MouseEventHandler<HTMLVideoElement> = e => {
|
||||
e.stopPropagation();
|
||||
const handler = onClick;
|
||||
if (handler) handler();
|
||||
};
|
||||
|
||||
const conditionalAttributes: React.VideoHTMLAttributes<HTMLVideoElement> = {};
|
||||
if (isIOS()) {
|
||||
conditionalAttributes.playsInline = true;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='extended-video-player'>
|
||||
<video
|
||||
ref={video}
|
||||
src={src}
|
||||
autoPlay
|
||||
role='button'
|
||||
tabIndex={0}
|
||||
aria-label={alt}
|
||||
title={alt}
|
||||
muted={muted}
|
||||
controls={controls}
|
||||
loop={!controls}
|
||||
onClick={handleClick}
|
||||
{...conditionalAttributes}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExtendedVideoPlayer;
|
|
@ -9,6 +9,7 @@ import { openModal, closeModal } from 'soapbox/actions/modals';
|
|||
import { useAppDispatch, useAppSelector, usePrevious } from 'soapbox/hooks';
|
||||
|
||||
import type { UnregisterCallback } from 'history';
|
||||
import type { ModalType } from 'soapbox/features/ui/components/modal_root';
|
||||
import type { ReducerCompose } from 'soapbox/reducers/compose';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -26,8 +27,8 @@ export const checkComposeContent = (compose?: ReturnType<typeof ReducerCompose>)
|
|||
|
||||
interface IModalRoot {
|
||||
onCancel?: () => void,
|
||||
onClose: (type?: string) => void,
|
||||
type: string,
|
||||
onClose: (type?: ModalType) => void,
|
||||
type: ModalType,
|
||||
}
|
||||
|
||||
const ModalRoot: React.FC<IModalRoot> = ({ children, onCancel, onClose, type }) => {
|
||||
|
|
|
@ -28,7 +28,7 @@ const SidebarNavigation = () => {
|
|||
const instance = useAppSelector((state) => state.instance);
|
||||
const settings = useAppSelector((state) => getSettings(state));
|
||||
const account = useOwnAccount();
|
||||
const notificationCount = useAppSelector((state) => state.notifications.get('unread'));
|
||||
const notificationCount = useAppSelector((state) => state.notifications.unread);
|
||||
const chatsCount = useAppSelector((state) => state.chats.items.reduce((acc, curr) => acc + Math.min(curr.unread || 0, 1), 0));
|
||||
const followRequestsCount = useAppSelector((state) => state.user_lists.follow_requests.items.count());
|
||||
const dashboardCount = useAppSelector((state) => state.admin.openReports.count() + state.admin.awaitingApproval.count());
|
||||
|
|
|
@ -13,7 +13,7 @@ const normalize = (notification: any) => {
|
|||
|
||||
return {
|
||||
// @ts-ignore
|
||||
notification: state.notifications.items.get(notification.id),
|
||||
notification: state.notifications.items.get(notification.id)!,
|
||||
state,
|
||||
};
|
||||
};
|
||||
|
|
Binary file not shown.
|
@ -0,0 +1,93 @@
|
|||
import { List as ImmutableList, Record as ImmutableRecord } from 'immutable';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import type { Attachment as AttachmentEntity } from 'soapbox/types/entities';
|
||||
|
||||
interface IPlaceholderMediaGallery {
|
||||
media: ImmutableList<AttachmentEntity>;
|
||||
defaultWidth?: number;
|
||||
}
|
||||
|
||||
const SizeData = ImmutableRecord({
|
||||
style: {} as React.CSSProperties,
|
||||
itemsDimensions: [] as Record<string, string>[],
|
||||
size: 1 as number,
|
||||
width: 0 as number,
|
||||
});
|
||||
|
||||
const PlaceholderMediaGallery: React.FC<IPlaceholderMediaGallery> = ({ media, defaultWidth }) => {
|
||||
const [width, setWidth] = useState(defaultWidth);
|
||||
|
||||
const handleRef = (node: HTMLDivElement) => {
|
||||
if (node) {
|
||||
setWidth(node.offsetWidth);
|
||||
}
|
||||
};
|
||||
|
||||
const getSizeData = (size: number) => {
|
||||
const style: React.CSSProperties = {};
|
||||
let itemsDimensions: Record<string, string>[] = [];
|
||||
|
||||
if (size === 1) {
|
||||
style.height = width! * 9 / 16;
|
||||
|
||||
itemsDimensions = [
|
||||
{ w: '100%', h: '100%' },
|
||||
];
|
||||
} else if (size === 2) {
|
||||
style.height = width! / 2;
|
||||
|
||||
itemsDimensions = [
|
||||
{ w: '50%', h: '100%', r: '2px' },
|
||||
{ w: '50%', h: '100%', l: '2px' },
|
||||
];
|
||||
} else if (size === 3) {
|
||||
style.height = width;
|
||||
|
||||
itemsDimensions = [
|
||||
{ w: '50%', h: '50%', b: '2px', r: '2px' },
|
||||
{ w: '50%', h: '50%', b: '2px', l: '2px' },
|
||||
{ w: '100%', h: '50%', t: '2px' },
|
||||
];
|
||||
} else if (size >= 4) {
|
||||
style.height = width;
|
||||
|
||||
itemsDimensions = [
|
||||
{ w: '50%', h: '50%', b: '2px', r: '2px' },
|
||||
{ w: '50%', h: '50%', b: '2px', l: '2px' },
|
||||
{ w: '50%', h: '50%', t: '2px', r: '2px' },
|
||||
{ w: '50%', h: '50%', t: '2px', l: '2px' },
|
||||
];
|
||||
}
|
||||
|
||||
return SizeData({
|
||||
style,
|
||||
itemsDimensions,
|
||||
size,
|
||||
width,
|
||||
});
|
||||
};
|
||||
|
||||
const renderItem = (dimensions: Record<string, string>, i: number) => {
|
||||
const width = dimensions.w;
|
||||
const height = dimensions.h;
|
||||
const top = dimensions.t || 'auto';
|
||||
const right = dimensions.r || 'auto';
|
||||
const bottom = dimensions.b || 'auto';
|
||||
const left = dimensions.l || 'auto';
|
||||
const float = dimensions.float as any || 'left';
|
||||
const position = dimensions.pos as any || 'relative';
|
||||
|
||||
return <div key={i} className='media-gallery__item' style={{ position, float, left, top, right, bottom, height, width }} />;
|
||||
};
|
||||
|
||||
const sizeData = getSizeData(media.size);
|
||||
|
||||
return (
|
||||
<div className='media-gallery media-gallery--placeholder' style={sizeData.get('style')} ref={handleRef}>
|
||||
{media.take(4).map((_, i) => renderItem(sizeData.get('itemsDimensions')[i], i))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PlaceholderMediaGallery;
|
Binary file not shown.
|
@ -3,10 +3,10 @@ import React from 'react';
|
|||
const emptyComponent = () => null;
|
||||
const noop = () => { };
|
||||
|
||||
interface BundleProps {
|
||||
export interface BundleProps {
|
||||
fetchComponent: () => Promise<any>,
|
||||
loading: React.ComponentType,
|
||||
error: React.ComponentType<{ onRetry: (props: BundleProps) => void }>,
|
||||
error: React.ComponentType<{ onRetry: (props?: BundleProps) => void }>,
|
||||
children: (mod: any) => React.ReactNode,
|
||||
renderDelay?: number,
|
||||
onFetch: () => void,
|
||||
|
@ -57,7 +57,7 @@ class Bundle extends React.PureComponent<BundleProps, BundleState> {
|
|||
}
|
||||
}
|
||||
|
||||
load = (props: BundleProps) => {
|
||||
load = (props?: BundleProps) => {
|
||||
const { fetchComponent, onFetch, onFetchSuccess, onFetchFail, renderDelay } = props || this.props;
|
||||
const cachedMod = Bundle.cache.get(fetchComponent);
|
||||
|
||||
|
|
|
@ -225,7 +225,6 @@ const MediaModal: React.FC<IMediaModal> = (props) => {
|
|||
muted
|
||||
controls={false}
|
||||
width={width}
|
||||
link={link}
|
||||
height={height}
|
||||
key={attachment.preview_url}
|
||||
alt={attachment.description}
|
||||
|
|
Binary file not shown.
|
@ -1,6 +1,7 @@
|
|||
import { connect } from 'react-redux';
|
||||
|
||||
import { fetchBundleRequest, fetchBundleSuccess, fetchBundleFail } from '../../../actions/bundles';
|
||||
import { fetchBundleRequest, fetchBundleSuccess, fetchBundleFail } from 'soapbox/actions/bundles';
|
||||
|
||||
import Bundle from '../components/bundle';
|
||||
|
||||
import type { AppDispatch } from 'soapbox/store';
|
||||
|
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in a new issue