[Glitch] [Proposal] Make able to write React in Typescript (#2190)

Port 4866812b28 to glitch-soc

Signed-off-by: Plastikmensch <plastikmensch@users.noreply.github.com>
lolsob-rspec
Plastikmensch 2023-04-26 21:30:41 +02:00 committed by GitHub
parent c6305b9817
commit c33ecc00fa
16 changed files with 105 additions and 92 deletions

View File

@ -23,6 +23,7 @@ export const PICTURE_IN_PICTURE_REMOVE = 'PICTURE_IN_PICTURE_REMOVE';
* @return {object} * @return {object}
*/ */
export const deployPictureInPicture = (statusId, accountId, playerType, props) => { export const deployPictureInPicture = (statusId, accountId, playerType, props) => {
// @ts-expect-error
return (dispatch, getState) => { return (dispatch, getState) => {
// Do not open a player for a toot that does not exist // Do not open a player for a toot that does not exist
if (getState().hasIn(['statuses', statusId])) { if (getState().hasIn(['statuses', statusId])) {

View File

@ -46,6 +46,7 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
connectStream(channelName, params, (dispatch, getState) => { connectStream(channelName, params, (dispatch, getState) => {
const locale = getState().getIn(['meta', 'locale']); const locale = getState().getIn(['meta', 'locale']);
// @ts-expect-error
let pollingId; let pollingId;
/** /**
@ -61,6 +62,7 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
onConnect() { onConnect() {
dispatch(connectTimeline(timelineId)); dispatch(connectTimeline(timelineId));
// @ts-expect-error
if (pollingId) { if (pollingId) {
clearTimeout(pollingId); clearTimeout(pollingId);
pollingId = null; pollingId = null;
@ -75,6 +77,7 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
dispatch(disconnectTimeline(timelineId)); dispatch(disconnectTimeline(timelineId));
if (options.fallback) { if (options.fallback) {
// @ts-expect-error
pollingId = setTimeout(() => useFallback(options.fallback), randomUpTo(40000)); pollingId = setTimeout(() => useFallback(options.fallback), randomUpTo(40000));
} }
}, },
@ -82,24 +85,30 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
onReceive (data) { onReceive (data) {
switch(data.event) { switch(data.event) {
case 'update': case 'update':
// @ts-expect-error
dispatch(updateTimeline(timelineId, JSON.parse(data.payload), options.accept)); dispatch(updateTimeline(timelineId, JSON.parse(data.payload), options.accept));
break; break;
case 'status.update': case 'status.update':
// @ts-expect-error
dispatch(updateStatus(JSON.parse(data.payload))); dispatch(updateStatus(JSON.parse(data.payload)));
break; break;
case 'delete': case 'delete':
dispatch(deleteFromTimelines(data.payload)); dispatch(deleteFromTimelines(data.payload));
break; break;
case 'notification': case 'notification':
// @ts-expect-error
dispatch(updateNotifications(JSON.parse(data.payload), messages, locale)); dispatch(updateNotifications(JSON.parse(data.payload), messages, locale));
break; break;
case 'conversation': case 'conversation':
// @ts-expect-error
dispatch(updateConversations(JSON.parse(data.payload))); dispatch(updateConversations(JSON.parse(data.payload)));
break; break;
case 'announcement': case 'announcement':
// @ts-expect-error
dispatch(updateAnnouncements(JSON.parse(data.payload))); dispatch(updateAnnouncements(JSON.parse(data.payload)));
break; break;
case 'announcement.reaction': case 'announcement.reaction':
// @ts-expect-error
dispatch(updateAnnouncementsReaction(JSON.parse(data.payload))); dispatch(updateAnnouncementsReaction(JSON.parse(data.payload)));
break; break;
case 'announcement.delete': case 'announcement.delete':
@ -115,7 +124,9 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
* @param {function(): void} done * @param {function(): void} done
*/ */
const refreshHomeTimelineAndNotification = (dispatch, done) => { const refreshHomeTimelineAndNotification = (dispatch, done) => {
// @ts-expect-error
dispatch(expandHomeTimeline({}, () => dispatch(expandHomeTimeline({}, () =>
// @ts-expect-error
dispatch(expandNotifications({}, () => dispatch(expandNotifications({}, () =>
dispatch(fetchAnnouncements(done)))))); dispatch(fetchAnnouncements(done))))));
}; };
@ -124,6 +135,7 @@ const refreshHomeTimelineAndNotification = (dispatch, done) => {
* @return {function(): void} * @return {function(): void}
*/ */
export const connectUserStream = () => export const connectUserStream = () =>
// @ts-expect-error
connectTimelineStream('home', 'user', {}, { fallback: refreshHomeTimelineAndNotification, fillGaps: fillHomeTimelineGaps }); connectTimelineStream('home', 'user', {}, { fallback: refreshHomeTimelineAndNotification, fillGaps: fillHomeTimelineGaps });
/** /**

View File

@ -36,7 +36,7 @@ const setCSRFHeader = () => {
ready(setCSRFHeader); ready(setCSRFHeader);
/** /**
* @param {() => import('immutable').Map} getState * @param {() => import('immutable').Map<string,any>} getState
* @returns {import('axios').RawAxiosRequestHeaders} * @returns {import('axios').RawAxiosRequestHeaders}
*/ */
const authorizationHeaderFromState = getState => { const authorizationHeaderFromState = getState => {
@ -52,7 +52,7 @@ const authorizationHeaderFromState = getState => {
}; };
/** /**
* @param {() => import('immutable').Map} getState * @param {() => import('immutable').Map<string,any>} getState
* @returns {import('axios').AxiosInstance} * @returns {import('axios').AxiosInstance}
*/ */
export default function api(getState) { export default function api(getState) {

View File

@ -1,79 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { autoPlayGif } from 'flavours/glitch/initial_state';
import classNames from 'classnames';
export default class Avatar extends React.PureComponent {
static propTypes = {
account: ImmutablePropTypes.map,
className: PropTypes.string,
size: PropTypes.number.isRequired,
style: PropTypes.object,
inline: PropTypes.bool,
animate: PropTypes.bool,
};
static defaultProps = {
animate: autoPlayGif,
size: 20,
inline: false,
};
state = {
hovering: false,
};
handleMouseEnter = () => {
if (this.props.animate) return;
this.setState({ hovering: true });
};
handleMouseLeave = () => {
if (this.props.animate) return;
this.setState({ hovering: false });
};
render () {
const {
account,
animate,
className,
inline,
size,
} = this.props;
const { hovering } = this.state;
const style = {
...this.props.style,
width: `${size}px`,
height: `${size}px`,
backgroundSize: `${size}px ${size}px`,
};
if (account) {
const src = account.get('avatar');
const staticSrc = account.get('avatar_static');
if (hovering || animate) {
style.backgroundImage = `url(${src})`;
} else {
style.backgroundImage = `url(${staticSrc})`;
}
}
return (
<div
className={classNames('account__avatar', { 'account__avatar-inline': inline }, className)}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
style={style}
data-avatar-of={account && `@${account.get('acct')}`}
role='img'
aria-label={account?.get('acct')}
/>
);
}
}

View File

@ -0,0 +1,48 @@
import * as React from 'react';
import classNames from 'classnames';
import { autoPlayGif } from 'flavours/glitch/initial_state';
import { useHovering } from 'hooks/useHovering';
import type { Account } from 'types/resources';
type Props = {
account: Account | undefined;
className?: string;
size: number;
style?: React.CSSProperties;
inline?: boolean;
}
export const Avatar: React.FC<Props> = ({
account,
className,
size = 20,
inline = false,
style: styleFromParent,
}) => {
const { hovering, handleMouseEnter, handleMouseLeave } = useHovering(autoPlayGif);
const style = {
...styleFromParent,
width: `${size}px`,
height: `${size}px`,
backgroundSize: `${size}px ${size}px`,
};
if (account) {
style.backgroundImage = `url(${account.get(hovering ? 'avatar' : 'avatar_static')})`;
}
return (
<div
className={classNames('account__avatar', { 'account__avatar-inline': inline }, className)}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
style={style}
data-avatar-of={account && `@${account.get('acct')}`}
role='img'
aria-label={account?.get('acct')}
/>
);
};
export default Avatar;

View File

@ -44,6 +44,7 @@ function Blurhash({
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
const imageData = new ImageData(pixels, width, height); const imageData = new ImageData(pixels, width, height);
// @ts-expect-error
ctx.putImageData(imageData, 0, 0); ctx.putImageData(imageData, 0, 0);
} catch (err) { } catch (err) {
console.error('Blurhash decoding failure', { err, hash }); console.error('Blurhash decoding failure', { err, hash });

View File

@ -50,12 +50,14 @@ export const accountsCountRenderer = (displayNumber, pluralReady) => (
/> />
); );
// @ts-expect-error
export const ImmutableHashtag = ({ hashtag }) => ( export const ImmutableHashtag = ({ hashtag }) => (
<Hashtag <Hashtag
name={hashtag.get('name')} name={hashtag.get('name')}
href={hashtag.get('url')} href={hashtag.get('url')}
to={`/tags/${hashtag.get('name')}`} to={`/tags/${hashtag.get('name')}`}
people={hashtag.getIn(['history', 0, 'accounts']) * 1 + hashtag.getIn(['history', 1, 'accounts']) * 1} people={hashtag.getIn(['history', 0, 'accounts']) * 1 + hashtag.getIn(['history', 1, 'accounts']) * 1}
// @ts-expect-error
history={hashtag.get('history').reverse().map((day) => day.get('uses')).toArray()} history={hashtag.get('history').reverse().map((day) => day.get('uses')).toArray()}
/> />
); );
@ -64,6 +66,7 @@ ImmutableHashtag.propTypes = {
hashtag: ImmutablePropTypes.map.isRequired, hashtag: ImmutablePropTypes.map.isRequired,
}; };
// @ts-expect-error
const Hashtag = ({ name, href, to, people, uses, history, className, description, withGraph }) => ( const Hashtag = ({ name, href, to, people, uses, history, className, description, withGraph }) => (
<div className={classNames('trends__item', className)}> <div className={classNames('trends__item', className)}>
<div className='trends__item__name'> <div className='trends__item__name'>

View File

@ -9,7 +9,7 @@ const emojis = {};
// decompress // decompress
Object.keys(shortCodesToEmojiData).forEach((shortCode) => { Object.keys(shortCodesToEmojiData).forEach((shortCode) => {
let [ let [
filenameData, // eslint-disable-line no-unused-vars filenameData, // eslint-disable-line @typescript-eslint/no-unused-vars
searchData, searchData,
] = shortCodesToEmojiData[shortCode]; ] = shortCodesToEmojiData[shortCode];
let [ let [

View File

@ -4,9 +4,9 @@
const [ const [
shortCodesToEmojiData, shortCodesToEmojiData,
skins, // eslint-disable-line no-unused-vars skins, // eslint-disable-line @typescript-eslint/no-unused-vars
categories, // eslint-disable-line no-unused-vars categories, // eslint-disable-line @typescript-eslint/no-unused-vars
short_names, // eslint-disable-line no-unused-vars short_names, // eslint-disable-line @typescript-eslint/no-unused-vars
emojisWithoutShortCodes, emojisWithoutShortCodes,
] = require('./emoji_compressed'); ] = require('./emoji_compressed');
const { unicodeToFilename } = require('./unicode_to_filename'); const { unicodeToFilename } = require('./unicode_to_filename');

View File

@ -51,6 +51,7 @@
* @property {boolean} activity_api_enabled * @property {boolean} activity_api_enabled
* @property {string} admin * @property {string} admin
* @property {boolean=} boost_modal * @property {boolean=} boost_modal
* @property {boolean=} favourite_modal
* @property {boolean} crop_images * @property {boolean} crop_images
* @property {boolean=} delete_modal * @property {boolean=} delete_modal
* @property {boolean=} disable_swiping * @property {boolean=} disable_swiping
@ -81,7 +82,9 @@
* @property {boolean=} use_pending_items * @property {boolean=} use_pending_items
* @property {string} version * @property {string} version
* @property {boolean} translation_enabled * @property {boolean} translation_enabled
* @property {object} local_settings * @property {string} status_page_url
* @property {boolean} system_emoji_font
* @property {string} default_content_type
*/ */
/** /**
@ -89,6 +92,9 @@
* @property {Record<string, Account>} accounts * @property {Record<string, Account>} accounts
* @property {InitialStateLanguage[]} languages * @property {InitialStateLanguage[]} languages
* @property {InitialStateMeta} meta * @property {InitialStateMeta} meta
* @property {object} local_settings
* @property {number} max_toot_chars
* @property {number} poll_limits
*/ */
const element = document.getElementById('initial-state'); const element = document.getElementById('initial-state');
@ -98,6 +104,7 @@ const initialState = element?.textContent && JSON.parse(element.textContent);
// Glitch-soc-specific “local settings” // Glitch-soc-specific “local settings”
if (initialState) { if (initialState) {
try { try {
// @ts-expect-error
initialState.local_settings = JSON.parse(localStorage.getItem('mastodon-settings')); initialState.local_settings = JSON.parse(localStorage.getItem('mastodon-settings'));
} catch (e) { } catch (e) {
initialState.local_settings = {}; initialState.local_settings = {};

View File

@ -36,6 +36,7 @@ export const layoutFromWindow = (layout_local_setting) => {
} }
}; };
// @ts-expect-error
const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
const listenerOptions = supportsPassiveEvents ? { passive: true } : false; const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
@ -45,7 +46,7 @@ let userTouching = false;
const touchListener = () => { const touchListener = () => {
userTouching = true; userTouching = true;
window.removeEventListener('touchstart', touchListener, listenerOptions); window.removeEventListener('touchstart', touchListener);
}; };
window.addEventListener('touchstart', touchListener, listenerOptions); window.addEventListener('touchstart', touchListener, listenerOptions);

View File

@ -59,6 +59,7 @@ const subscribe = ({ channelName, params, onConnect }) => {
subscriptionCounters[key] = subscriptionCounters[key] || 0; subscriptionCounters[key] = subscriptionCounters[key] || 0;
if (subscriptionCounters[key] === 0) { if (subscriptionCounters[key] === 0) {
// @ts-expect-error
sharedConnection.send(JSON.stringify({ type: 'subscribe', stream: channelName, ...params })); sharedConnection.send(JSON.stringify({ type: 'subscribe', stream: channelName, ...params }));
} }
@ -74,7 +75,9 @@ const unsubscribe = ({ channelName, params, onDisconnect }) => {
subscriptionCounters[key] = subscriptionCounters[key] || 1; subscriptionCounters[key] = subscriptionCounters[key] || 1;
// @ts-expect-error
if (subscriptionCounters[key] === 1 && sharedConnection.readyState === WebSocketClient.OPEN) { if (subscriptionCounters[key] === 1 && sharedConnection.readyState === WebSocketClient.OPEN) {
// @ts-expect-error
sharedConnection.send(JSON.stringify({ type: 'unsubscribe', stream: channelName, ...params })); sharedConnection.send(JSON.stringify({ type: 'unsubscribe', stream: channelName, ...params }));
} }
@ -87,6 +90,7 @@ const sharedCallbacks = {
subscriptions.forEach(subscription => subscribe(subscription)); subscriptions.forEach(subscription => subscribe(subscription));
}, },
// @ts-expect-error
received (data) { received (data) {
const { stream } = data; const { stream } = data;
@ -138,6 +142,7 @@ const channelNameWithInlineParams = (channelName, params) => {
* @param {function(Function, Function): { onConnect: (function(): void), onReceive: (function(StreamEvent): void), onDisconnect: (function(): void) }} callbacks * @param {function(Function, Function): { onConnect: (function(): void), onReceive: (function(StreamEvent): void), onDisconnect: (function(): void) }} callbacks
* @return {function(): void} * @return {function(): void}
*/ */
// @ts-expect-error
export const connectStream = (channelName, params, callbacks) => (dispatch, getState) => { export const connectStream = (channelName, params, callbacks) => (dispatch, getState) => {
const streamingAPIBaseURL = getState().getIn(['meta', 'streaming_api_base_url']); const streamingAPIBaseURL = getState().getIn(['meta', 'streaming_api_base_url']);
const accessToken = getState().getIn(['meta', 'access_token']); const accessToken = getState().getIn(['meta', 'access_token']);
@ -227,14 +232,19 @@ const handleEventSourceMessage = (e, received) => {
const createConnection = (streamingAPIBaseURL, accessToken, channelName, { connected, received, disconnected, reconnected }) => { const createConnection = (streamingAPIBaseURL, accessToken, channelName, { connected, received, disconnected, reconnected }) => {
const params = channelName.split('&'); const params = channelName.split('&');
// @ts-expect-error
channelName = params.shift(); channelName = params.shift();
if (streamingAPIBaseURL.startsWith('ws')) { if (streamingAPIBaseURL.startsWith('ws')) {
// @ts-expect-error
const ws = new WebSocketClient(`${streamingAPIBaseURL}/api/v1/streaming/?${params.join('&')}`, accessToken); const ws = new WebSocketClient(`${streamingAPIBaseURL}/api/v1/streaming/?${params.join('&')}`, accessToken);
// @ts-expect-error
ws.onopen = connected; ws.onopen = connected;
ws.onmessage = e => received(JSON.parse(e.data)); ws.onmessage = e => received(JSON.parse(e.data));
// @ts-expect-error
ws.onclose = disconnected; ws.onclose = disconnected;
// @ts-expect-error
ws.onreconnect = reconnected; ws.onreconnect = reconnected;
return ws; return ws;

View File

@ -1,3 +0,0 @@
export default function uuid(a) {
return a ? (a^Math.random() * 16 >> a / 4).toString(16) : ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, uuid);
}

View File

@ -0,0 +1,3 @@
export default function uuid(a?: string): string {
return a ? ((a as any as number) ^ Math.random() * 16 >> (a as any as number) / 4).toString(16) : ('' + 1e7 + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, uuid);
}

View File

@ -87,6 +87,7 @@
* @property {Record<string, Account>} accounts * @property {Record<string, Account>} accounts
* @property {InitialStateLanguage[]} languages * @property {InitialStateLanguage[]} languages
* @property {InitialStateMeta} meta * @property {InitialStateMeta} meta
* @property {number} max_toot_chars
*/ */
const element = document.getElementById('initial-state'); const element = document.getElementById('initial-state');

View File

@ -7,7 +7,15 @@
"noEmit": true, "noEmit": true,
"strict": true, "strict": true,
"esModuleInterop": true, "esModuleInterop": true,
"skipLibCheck": true "skipLibCheck": true,
"baseUrl": ".",
"paths": {
"*": ["app/javascript/*"]
}
}, },
"include": ["app/javascript/mastodon", "app/javascript/packs"] "include": [
"app/javascript/mastodon",
"app/javascript/flavours/glitch",
"app/javascript/packs"
]
} }