Merge branch 'ts' into 'develop'
JS -> TS, FC See merge request soapbox-pub/soapbox!1634
This commit is contained in:
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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}
|
||||
@@ -298,4 +297,4 @@ const MediaModal: React.FC<IMediaModal> = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default MediaModal;
|
||||
export default MediaModal;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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());
|
||||
Reference in New Issue
Block a user