mirror of
https://git.bsd.gay/fef/nyastodon.git
synced 2024-12-24 19:23:41 +01:00
[Glitch] [Proposal] Make able to write React in Typescript (#2190)
Port 4520e6473a
to glitch-soc
Signed-off-by: Plastikmensch <plastikmensch@users.noreply.github.com>
This commit is contained in:
parent
678480d4d3
commit
1565af1caf
16 changed files with 105 additions and 92 deletions
|
@ -23,6 +23,7 @@ export const PICTURE_IN_PICTURE_REMOVE = 'PICTURE_IN_PICTURE_REMOVE';
|
|||
* @return {object}
|
||||
*/
|
||||
export const deployPictureInPicture = (statusId, accountId, playerType, props) => {
|
||||
// @ts-expect-error
|
||||
return (dispatch, getState) => {
|
||||
// Do not open a player for a toot that does not exist
|
||||
if (getState().hasIn(['statuses', statusId])) {
|
||||
|
|
|
@ -46,6 +46,7 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
|
|||
connectStream(channelName, params, (dispatch, getState) => {
|
||||
const locale = getState().getIn(['meta', 'locale']);
|
||||
|
||||
// @ts-expect-error
|
||||
let pollingId;
|
||||
|
||||
/**
|
||||
|
@ -61,6 +62,7 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
|
|||
onConnect() {
|
||||
dispatch(connectTimeline(timelineId));
|
||||
|
||||
// @ts-expect-error
|
||||
if (pollingId) {
|
||||
clearTimeout(pollingId);
|
||||
pollingId = null;
|
||||
|
@ -75,6 +77,7 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
|
|||
dispatch(disconnectTimeline(timelineId));
|
||||
|
||||
if (options.fallback) {
|
||||
// @ts-expect-error
|
||||
pollingId = setTimeout(() => useFallback(options.fallback), randomUpTo(40000));
|
||||
}
|
||||
},
|
||||
|
@ -82,24 +85,30 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
|
|||
onReceive (data) {
|
||||
switch(data.event) {
|
||||
case 'update':
|
||||
// @ts-expect-error
|
||||
dispatch(updateTimeline(timelineId, JSON.parse(data.payload), options.accept));
|
||||
break;
|
||||
case 'status.update':
|
||||
// @ts-expect-error
|
||||
dispatch(updateStatus(JSON.parse(data.payload)));
|
||||
break;
|
||||
case 'delete':
|
||||
dispatch(deleteFromTimelines(data.payload));
|
||||
break;
|
||||
case 'notification':
|
||||
// @ts-expect-error
|
||||
dispatch(updateNotifications(JSON.parse(data.payload), messages, locale));
|
||||
break;
|
||||
case 'conversation':
|
||||
// @ts-expect-error
|
||||
dispatch(updateConversations(JSON.parse(data.payload)));
|
||||
break;
|
||||
case 'announcement':
|
||||
// @ts-expect-error
|
||||
dispatch(updateAnnouncements(JSON.parse(data.payload)));
|
||||
break;
|
||||
case 'announcement.reaction':
|
||||
// @ts-expect-error
|
||||
dispatch(updateAnnouncementsReaction(JSON.parse(data.payload)));
|
||||
break;
|
||||
case 'announcement.delete':
|
||||
|
@ -115,7 +124,9 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
|
|||
* @param {function(): void} done
|
||||
*/
|
||||
const refreshHomeTimelineAndNotification = (dispatch, done) => {
|
||||
// @ts-expect-error
|
||||
dispatch(expandHomeTimeline({}, () =>
|
||||
// @ts-expect-error
|
||||
dispatch(expandNotifications({}, () =>
|
||||
dispatch(fetchAnnouncements(done))))));
|
||||
};
|
||||
|
@ -124,6 +135,7 @@ const refreshHomeTimelineAndNotification = (dispatch, done) => {
|
|||
* @return {function(): void}
|
||||
*/
|
||||
export const connectUserStream = () =>
|
||||
// @ts-expect-error
|
||||
connectTimelineStream('home', 'user', {}, { fallback: refreshHomeTimelineAndNotification, fillGaps: fillHomeTimelineGaps });
|
||||
|
||||
/**
|
||||
|
|
|
@ -36,7 +36,7 @@ const setCSRFHeader = () => {
|
|||
ready(setCSRFHeader);
|
||||
|
||||
/**
|
||||
* @param {() => import('immutable').Map} getState
|
||||
* @param {() => import('immutable').Map<string,any>} getState
|
||||
* @returns {import('axios').RawAxiosRequestHeaders}
|
||||
*/
|
||||
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}
|
||||
*/
|
||||
export default function api(getState) {
|
||||
|
|
|
@ -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')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
48
app/javascript/flavours/glitch/components/avatar.tsx
Normal file
48
app/javascript/flavours/glitch/components/avatar.tsx
Normal 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;
|
|
@ -44,6 +44,7 @@ function Blurhash({
|
|||
const ctx = canvas.getContext('2d');
|
||||
const imageData = new ImageData(pixels, width, height);
|
||||
|
||||
// @ts-expect-error
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
} catch (err) {
|
||||
console.error('Blurhash decoding failure', { err, hash });
|
||||
|
|
|
@ -50,12 +50,14 @@ export const accountsCountRenderer = (displayNumber, pluralReady) => (
|
|||
/>
|
||||
);
|
||||
|
||||
// @ts-expect-error
|
||||
export const ImmutableHashtag = ({ hashtag }) => (
|
||||
<Hashtag
|
||||
name={hashtag.get('name')}
|
||||
href={hashtag.get('url')}
|
||||
to={`/tags/${hashtag.get('name')}`}
|
||||
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()}
|
||||
/>
|
||||
);
|
||||
|
@ -64,6 +66,7 @@ ImmutableHashtag.propTypes = {
|
|||
hashtag: ImmutablePropTypes.map.isRequired,
|
||||
};
|
||||
|
||||
// @ts-expect-error
|
||||
const Hashtag = ({ name, href, to, people, uses, history, className, description, withGraph }) => (
|
||||
<div className={classNames('trends__item', className)}>
|
||||
<div className='trends__item__name'>
|
||||
|
|
|
@ -9,7 +9,7 @@ const emojis = {};
|
|||
// decompress
|
||||
Object.keys(shortCodesToEmojiData).forEach((shortCode) => {
|
||||
let [
|
||||
filenameData, // eslint-disable-line no-unused-vars
|
||||
filenameData, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
searchData,
|
||||
] = shortCodesToEmojiData[shortCode];
|
||||
let [
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
|
||||
const [
|
||||
shortCodesToEmojiData,
|
||||
skins, // eslint-disable-line no-unused-vars
|
||||
categories, // eslint-disable-line no-unused-vars
|
||||
short_names, // eslint-disable-line no-unused-vars
|
||||
skins, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
categories, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
short_names, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
emojisWithoutShortCodes,
|
||||
] = require('./emoji_compressed');
|
||||
const { unicodeToFilename } = require('./unicode_to_filename');
|
||||
|
|
|
@ -51,6 +51,7 @@
|
|||
* @property {boolean} activity_api_enabled
|
||||
* @property {string} admin
|
||||
* @property {boolean=} boost_modal
|
||||
* @property {boolean=} favourite_modal
|
||||
* @property {boolean} crop_images
|
||||
* @property {boolean=} delete_modal
|
||||
* @property {boolean=} disable_swiping
|
||||
|
@ -81,7 +82,9 @@
|
|||
* @property {boolean=} use_pending_items
|
||||
* @property {string} version
|
||||
* @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 {InitialStateLanguage[]} languages
|
||||
* @property {InitialStateMeta} meta
|
||||
* @property {object} local_settings
|
||||
* @property {number} max_toot_chars
|
||||
* @property {number} poll_limits
|
||||
*/
|
||||
|
||||
const element = document.getElementById('initial-state');
|
||||
|
@ -98,6 +104,7 @@ const initialState = element?.textContent && JSON.parse(element.textContent);
|
|||
// Glitch-soc-specific “local settings”
|
||||
if (initialState) {
|
||||
try {
|
||||
// @ts-expect-error
|
||||
initialState.local_settings = JSON.parse(localStorage.getItem('mastodon-settings'));
|
||||
} catch (e) {
|
||||
initialState.local_settings = {};
|
||||
|
|
|
@ -36,6 +36,7 @@ export const layoutFromWindow = (layout_local_setting) => {
|
|||
}
|
||||
};
|
||||
|
||||
// @ts-expect-error
|
||||
const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
||||
|
||||
const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
|
||||
|
@ -45,7 +46,7 @@ let userTouching = false;
|
|||
const touchListener = () => {
|
||||
userTouching = true;
|
||||
|
||||
window.removeEventListener('touchstart', touchListener, listenerOptions);
|
||||
window.removeEventListener('touchstart', touchListener);
|
||||
};
|
||||
|
||||
window.addEventListener('touchstart', touchListener, listenerOptions);
|
||||
|
|
|
@ -59,6 +59,7 @@ const subscribe = ({ channelName, params, onConnect }) => {
|
|||
subscriptionCounters[key] = subscriptionCounters[key] || 0;
|
||||
|
||||
if (subscriptionCounters[key] === 0) {
|
||||
// @ts-expect-error
|
||||
sharedConnection.send(JSON.stringify({ type: 'subscribe', stream: channelName, ...params }));
|
||||
}
|
||||
|
||||
|
@ -74,7 +75,9 @@ const unsubscribe = ({ channelName, params, onDisconnect }) => {
|
|||
|
||||
subscriptionCounters[key] = subscriptionCounters[key] || 1;
|
||||
|
||||
// @ts-expect-error
|
||||
if (subscriptionCounters[key] === 1 && sharedConnection.readyState === WebSocketClient.OPEN) {
|
||||
// @ts-expect-error
|
||||
sharedConnection.send(JSON.stringify({ type: 'unsubscribe', stream: channelName, ...params }));
|
||||
}
|
||||
|
||||
|
@ -87,6 +90,7 @@ const sharedCallbacks = {
|
|||
subscriptions.forEach(subscription => subscribe(subscription));
|
||||
},
|
||||
|
||||
// @ts-expect-error
|
||||
received (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
|
||||
* @return {function(): void}
|
||||
*/
|
||||
// @ts-expect-error
|
||||
export const connectStream = (channelName, params, callbacks) => (dispatch, getState) => {
|
||||
const streamingAPIBaseURL = getState().getIn(['meta', 'streaming_api_base_url']);
|
||||
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 params = channelName.split('&');
|
||||
|
||||
// @ts-expect-error
|
||||
channelName = params.shift();
|
||||
|
||||
if (streamingAPIBaseURL.startsWith('ws')) {
|
||||
// @ts-expect-error
|
||||
const ws = new WebSocketClient(`${streamingAPIBaseURL}/api/v1/streaming/?${params.join('&')}`, accessToken);
|
||||
|
||||
// @ts-expect-error
|
||||
ws.onopen = connected;
|
||||
ws.onmessage = e => received(JSON.parse(e.data));
|
||||
// @ts-expect-error
|
||||
ws.onclose = disconnected;
|
||||
// @ts-expect-error
|
||||
ws.onreconnect = reconnected;
|
||||
|
||||
return ws;
|
||||
|
|
|
@ -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);
|
||||
}
|
3
app/javascript/flavours/glitch/uuid.ts
Normal file
3
app/javascript/flavours/glitch/uuid.ts
Normal 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);
|
||||
}
|
|
@ -87,6 +87,7 @@
|
|||
* @property {Record<string, Account>} accounts
|
||||
* @property {InitialStateLanguage[]} languages
|
||||
* @property {InitialStateMeta} meta
|
||||
* @property {number} max_toot_chars
|
||||
*/
|
||||
|
||||
const element = document.getElementById('initial-state');
|
||||
|
|
|
@ -7,7 +7,15 @@
|
|||
"noEmit": true,
|
||||
"strict": 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"
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue