Merge branch 'ts' into 'develop'

JS -> TS, FC

See merge request soapbox-pub/soapbox!1634
This commit is contained in:
marcin mikołajczak
2022-11-12 15:16:32 +00:00
16 changed files with 254 additions and 243 deletions

View File

@@ -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,
};
};

View File

@@ -1,96 +0,0 @@
import { Map as ImmutableMap } from 'immutable';
import PropTypes from 'prop-types';
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
export default class PlaceholderMediaGallery extends React.Component {
static propTypes = {
media: ImmutablePropTypes.map.isRequired,
defaultWidth: PropTypes.number,
}
state = {
width: this.props.defaultWidth,
};
handleRef = (node) => {
if (node) {
this.setState({
width: node.offsetWidth,
});
}
}
getSizeData = size => {
const { defaultWidth } = this.props;
const width = this.state.width || defaultWidth;
const style = {};
let itemsDimensions = [];
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 ImmutableMap({
style,
itemsDimensions,
size,
width,
});
}
renderItem = (dimensions, i) => {
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 || 'left';
const position = dimensions.pos || 'relative';
return <div key={i} className='media-gallery__item' style={{ position, float, left, top, right, bottom, height, width }} />;
}
render() {
const { media } = this.props;
const sizeData = this.getSizeData(media.size);
return (
<div className='media-gallery media-gallery--placeholder' style={sizeData.get('style')} ref={this.handleRef}>
{media.take(4).map((_, i) => this.renderItem(sizeData.get('itemsDimensions')[i], i))}
</div>
);
}
}

View File

@@ -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;

View File

@@ -1,6 +1,6 @@
export const PLACEHOLDER_CHAR = '█';
export const generateText = length => {
export const generateText = (length: number) => {
let text = '';
for (let i = 0; i < length; i++) {
@@ -11,6 +11,6 @@ export const generateText = length => {
};
// https://stackoverflow.com/a/7228322/8811886
export const randomIntFromInterval = (min, max) => {
export const randomIntFromInterval = (min: number, max: number) => {
return Math.floor(Math.random() * (max - min + 1) + min);
};

View File

@@ -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);

View File

@@ -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}
@@ -298,4 +297,4 @@ const MediaModal: React.FC<IMediaModal> = (props) => {
);
};
export default MediaModal;
export default MediaModal;

View File

@@ -35,6 +35,7 @@ import {
} from 'soapbox/features/ui/util/async-components';
import BundleContainer from '../containers/bundle_container';
import { BundleProps } from './bundle';
import BundleModalError from './bundle_modal_error';
import ModalLoading from './modal_loading';
@@ -71,19 +72,21 @@ const MODAL_COMPONENTS = {
'ACCOUNT_MODERATION': AccountModerationModal,
};
export default class ModalRoot extends React.PureComponent {
export type ModalType = keyof typeof MODAL_COMPONENTS | null;
static propTypes = {
type: PropTypes.string,
props: PropTypes.object,
onClose: PropTypes.func.isRequired,
};
interface IModalRoot {
type: ModalType,
props?: Record<string, any> | null,
onClose: (type?: ModalType) => void,
}
export default class ModalRoot extends React.PureComponent<IModalRoot> {
getSnapshotBeforeUpdate() {
return { visible: !!this.props.type };
}
componentDidUpdate(prevProps, prevState, { visible }) {
componentDidUpdate(prevProps: IModalRoot, prevState: any, { visible }: any) {
if (visible) {
document.body.classList.add('with-modals');
} else {
@@ -91,15 +94,15 @@ export default class ModalRoot extends React.PureComponent {
}
}
renderLoading = modalId => () => {
renderLoading = (modalId: string) => () => {
return !['MEDIA', 'VIDEO', 'BOOST', 'CONFIRM', 'ACTIONS'].includes(modalId) ? <ModalLoading /> : null;
}
renderError = (props) => {
renderError: React.ComponentType<{ onRetry: (props?: BundleProps) => void }> = (props) => {
return <BundleModalError {...props} onClose={this.onClickClose} />;
}
onClickClose = (_) => {
onClickClose = (_?: ModalType) => {
const { onClose, type } = this.props;
onClose(type);
}

View File

@@ -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';

View File

@@ -4,22 +4,24 @@ import { cancelReplyCompose } from 'soapbox/actions/compose';
import { closeModal } from 'soapbox/actions/modals';
import { cancelReport } from 'soapbox/actions/reports';
import ModalRoot from '../components/modal_root';
import ModalRoot, { ModalType } from '../components/modal_root';
const mapStateToProps = state => {
const modal = state.get('modals').last({
import type { AppDispatch, RootState } from 'soapbox/store';
const mapStateToProps = (state: RootState) => {
const modal = state.modals.last({
modalType: null,
modalProps: {},
});
return {
type: modal.modalType,
type: modal.modalType as ModalType,
props: modal.modalProps,
};
};
const mapDispatchToProps = (dispatch) => ({
onClose(type) {
const mapDispatchToProps = (dispatch: AppDispatch) => ({
onClose(type?: ModalType) {
switch (type) {
case 'COMPOSE':
dispatch(cancelReplyCompose());