Move more modules from flavours/glitch/utils to flavours/glitch

This commit is contained in:
Claire
2022-10-11 11:39:52 +02:00
parent 2535ec4fcb
commit b75bf336bd
42 changed files with 36 additions and 36 deletions

View File

@ -1,47 +0,0 @@
import 'intl';
import 'intl/locale-data/jsonp/en';
import 'es6-symbol/implement';
import includes from 'array-includes';
import assign from 'object-assign';
import values from 'object.values';
import isNaN from 'is-nan';
import { decode as decodeBase64 } from './base64';
import promiseFinally from 'promise.prototype.finally';
if (!Array.prototype.includes) {
includes.shim();
}
if (!Object.assign) {
Object.assign = assign;
}
if (!Object.values) {
values.shim();
}
if (!Number.isNaN) {
Number.isNaN = isNaN;
}
promiseFinally.shim();
if (!HTMLCanvasElement.prototype.toBlob) {
const BASE64_MARKER = ';base64,';
Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
value(callback, type = 'image/png', quality) {
const dataURL = this.toDataURL(type, quality);
let data;
if (dataURL.indexOf(BASE64_MARKER) >= 0) {
const [, base64] = dataURL.split(BASE64_MARKER);
data = decodeBase64(base64);
} else {
[, data] = dataURL.split(',');
}
callback(new Blob([data], { type }));
},
});
}

View File

@ -1,11 +0,0 @@
export default function compareId (id1, id2) {
if (id1 === id2) {
return 0;
}
if (id1.length === id2.length) {
return id1 > id2 ? 1 : -1;
} else {
return id1.length > id2.length ? 1 : -1;
}
};

View File

@ -1,5 +0,0 @@
import 'intersection-observer';
import 'requestidlecallback';
import objectFitImages from 'object-fit-images';
objectFitImages();

View File

@ -1,43 +0,0 @@
import { supportsPassiveEvents } from 'detect-passive-events';
import { forceSingleColumn } from 'flavours/glitch/initial_state';
const LAYOUT_BREAKPOINT = 630;
export const isMobile = width => width <= LAYOUT_BREAKPOINT;
export const layoutFromWindow = (layout_local_setting) => {
switch (layout_local_setting) {
case 'multiple':
return 'multi-column';
case 'single':
if (isMobile(window.innerWidth)) {
return 'mobile';
} else {
return 'single-column';
}
default:
if (isMobile(window.innerWidth)) {
return 'mobile';
} else if (forceSingleColumn) {
return 'single-column';
} else {
return 'multi-column';
}
}
};
const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
let userTouching = false;
let listenerOptions = supportsPassiveEvents ? { passive: true } : false;
const touchListener = () => {
userTouching = true;
window.removeEventListener('touchstart', touchListener, listenerOptions);
};
window.addEventListener('touchstart', touchListener, listenerOptions);
export const isUserTouching = () => userTouching;
export const isIOS = () => iOS;

View File

@ -1,16 +0,0 @@
// On KaiOS, we may not be able to use a mouse cursor or navigate using Tab-based focus, so we install
// special left/right focus navigation keyboard listeners, at least on public pages (i.e. so folks
// can at least log in using KaiOS devices).
function importArrowKeyNavigation() {
return import(/* webpackChunkName: "arrow-key-navigation" */ 'arrow-key-navigation');
}
export default function loadKeyboardExtensions() {
if (/KAIOS/.test(navigator.userAgent)) {
return importArrowKeyNavigation().then(arrowKeyNav => {
arrowKeyNav.register();
});
}
return Promise.resolve();
}

View File

@ -1,42 +0,0 @@
// Convenience function to load polyfills and return a promise when it's done.
// If there are no polyfills, then this is just Promise.resolve() which means
// it will execute in the same tick of the event loop (i.e. near-instant).
function importBasePolyfills() {
return import(/* webpackChunkName: "base_polyfills" */ './base_polyfills');
}
function importExtraPolyfills() {
return import(/* webpackChunkName: "extra_polyfills" */ './extra_polyfills');
}
function loadPolyfills() {
const needsBasePolyfills = !(
Array.prototype.includes &&
HTMLCanvasElement.prototype.toBlob &&
window.Intl &&
Number.isNaN &&
Object.assign &&
Object.values &&
window.Symbol &&
Promise.prototype.finally
);
// Latest version of Firefox and Safari do not have IntersectionObserver.
// Edge does not have requestIdleCallback and object-fit CSS property.
// This avoids shipping them all the polyfills.
const needsExtraPolyfills = !(
window.IntersectionObserver &&
window.IntersectionObserverEntry &&
'isIntersecting' in IntersectionObserverEntry.prototype &&
window.requestIdleCallback &&
'object-fit' in (new Image()).style
);
return Promise.all([
needsBasePolyfills && importBasePolyfills(),
needsExtraPolyfills && importExtraPolyfills(),
]);
}
export default loadPolyfills;

View File

@ -1,57 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { setupBrowserNotifications } from 'flavours/glitch/actions/notifications';
import Mastodon, { store } from 'flavours/glitch/containers/mastodon';
import ready from 'flavours/glitch/ready';
const perf = require('flavours/glitch/utils/performance');
/**
* @returns {Promise<void>}
*/
function main() {
perf.start('main()');
if (window.history && history.replaceState) {
const { pathname, search, hash } = window.location;
const path = pathname + search + hash;
if (!(/^\/web($|\/)/).test(path)) {
history.replaceState(null, document.title, `/web${path}`);
}
}
return ready(async () => {
const mountNode = document.getElementById('mastodon');
const props = JSON.parse(mountNode.getAttribute('data-props'));
ReactDOM.render(<Mastodon {...props} />, mountNode);
store.dispatch(setupBrowserNotifications());
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
const [{ Workbox }, { me }] = await Promise.all([
import('workbox-window'),
import('mastodon/initial_state'),
]);
const wb = new Workbox('/sw.js');
try {
await wb.register();
} catch (err) {
console.error(err);
return;
}
if (me) {
const registerPushNotifications = await import('flavours/glitch/actions/push_notifications');
store.dispatch(registerPushNotifications.register());
}
}
perf.stop('main()');
});
}
export default main;

View File

@ -1,31 +0,0 @@
//
// Tools for performance debugging, only enabled in development mode.
// Open up Chrome Dev Tools, then Timeline, then User Timing to see output.
// Also see config/webpack/loaders/mark.js for the webpack loader marks.
//
let marky;
if (process.env.NODE_ENV === 'development') {
if (typeof performance !== 'undefined' && performance.setResourceTimingBufferSize) {
// Increase Firefox's performance entry limit; otherwise it's capped to 150.
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1331135
performance.setResourceTimingBufferSize(Infinity);
}
marky = require('marky');
// allows us to easily do e.g. ReactPerf.printWasted() while debugging
//window.ReactPerf = require('react-addons-perf');
//window.ReactPerf.start();
}
export function start(name) {
if (process.env.NODE_ENV === 'development') {
marky.mark(name);
}
}
export function stop(name) {
if (process.env.NODE_ENV === 'development') {
marky.stop(name);
}
}

View File

@ -1,32 +0,0 @@
const easingOutQuint = (x, t, b, c, d) => c * ((t = t / d - 1) * t * t * t * t + 1) + b;
const scroll = (node, key, target) => {
const startTime = Date.now();
const offset = node[key];
const gap = target - offset;
const duration = 1000;
let interrupt = false;
const step = () => {
const elapsed = Date.now() - startTime;
const percentage = elapsed / duration;
if (percentage > 1 || interrupt) {
return;
}
node[key] = easingOutQuint(0, elapsed, offset, gap, duration);
requestAnimationFrame(step);
};
step();
return () => {
interrupt = true;
};
};
const isScrollBehaviorSupported = 'scrollBehavior' in document.documentElement.style;
export const scrollRight = (node, position) => isScrollBehaviorSupported ? node.scrollTo({ left: position, behavior: 'smooth' }) : scroll(node, 'scrollLeft', position);
export const scrollTop = (node) => isScrollBehaviorSupported ? node.scrollTo({ top: 0, behavior: 'smooth' }) : scroll(node, 'scrollTop', 0);

View File

@ -1,47 +0,0 @@
export default class Settings {
constructor(keyBase = null) {
this.keyBase = keyBase;
}
generateKey(id) {
return this.keyBase ? [this.keyBase, `id${id}`].join('.') : id;
}
set(id, data) {
const key = this.generateKey(id);
try {
const encodedData = JSON.stringify(data);
localStorage.setItem(key, encodedData);
return data;
} catch (e) {
return null;
}
}
get(id) {
const key = this.generateKey(id);
try {
const rawData = localStorage.getItem(key);
return JSON.parse(rawData);
} catch (e) {
return null;
}
}
remove(id) {
const data = this.get(id);
if (data) {
const key = this.generateKey(id);
try {
localStorage.removeItem(key);
} catch (e) {
}
}
return data;
}
}
export const pushNotificationsSetting = new Settings('mastodon_push_notification_data');
export const tagHistory = new Settings('mastodon_tag_history');

View File

@ -1,265 +0,0 @@
// @ts-check
import WebSocketClient from '@gamestdio/websocket';
/**
* @type {WebSocketClient | undefined}
*/
let sharedConnection;
/**
* @typedef Subscription
* @property {string} channelName
* @property {Object.<string, string>} params
* @property {function(): void} onConnect
* @property {function(StreamEvent): void} onReceive
* @property {function(): void} onDisconnect
*/
/**
* @typedef StreamEvent
* @property {string} event
* @property {object} payload
*/
/**
* @type {Array.<Subscription>}
*/
const subscriptions = [];
/**
* @type {Object.<string, number>}
*/
const subscriptionCounters = {};
/**
* @param {Subscription} subscription
*/
const addSubscription = subscription => {
subscriptions.push(subscription);
};
/**
* @param {Subscription} subscription
*/
const removeSubscription = subscription => {
const index = subscriptions.indexOf(subscription);
if (index !== -1) {
subscriptions.splice(index, 1);
}
};
/**
* @param {Subscription} subscription
*/
const subscribe = ({ channelName, params, onConnect }) => {
const key = channelNameWithInlineParams(channelName, params);
subscriptionCounters[key] = subscriptionCounters[key] || 0;
if (subscriptionCounters[key] === 0) {
sharedConnection.send(JSON.stringify({ type: 'subscribe', stream: channelName, ...params }));
}
subscriptionCounters[key] += 1;
onConnect();
};
/**
* @param {Subscription} subscription
*/
const unsubscribe = ({ channelName, params, onDisconnect }) => {
const key = channelNameWithInlineParams(channelName, params);
subscriptionCounters[key] = subscriptionCounters[key] || 1;
if (subscriptionCounters[key] === 1 && sharedConnection.readyState === WebSocketClient.OPEN) {
sharedConnection.send(JSON.stringify({ type: 'unsubscribe', stream: channelName, ...params }));
}
subscriptionCounters[key] -= 1;
onDisconnect();
};
const sharedCallbacks = {
connected () {
subscriptions.forEach(subscription => subscribe(subscription));
},
received (data) {
const { stream } = data;
subscriptions.filter(({ channelName, params }) => {
const streamChannelName = stream[0];
if (stream.length === 1) {
return channelName === streamChannelName;
}
const streamIdentifier = stream[1];
if (['hashtag', 'hashtag:local'].includes(channelName)) {
return channelName === streamChannelName && params.tag === streamIdentifier;
} else if (channelName === 'list') {
return channelName === streamChannelName && params.list === streamIdentifier;
}
return false;
}).forEach(subscription => {
subscription.onReceive(data);
});
},
disconnected () {
subscriptions.forEach(subscription => unsubscribe(subscription));
},
reconnected () {
},
};
/**
* @param {string} channelName
* @param {Object.<string, string>} params
* @return {string}
*/
const channelNameWithInlineParams = (channelName, params) => {
if (Object.keys(params).length === 0) {
return channelName;
}
return `${channelName}&${Object.keys(params).map(key => `${key}=${params[key]}`).join('&')}`;
};
/**
* @param {string} channelName
* @param {Object.<string, string>} params
* @param {function(Function, Function): { onConnect: (function(): void), onReceive: (function(StreamEvent): void), onDisconnect: (function(): void) }} callbacks
* @return {function(): void}
*/
export const connectStream = (channelName, params, callbacks) => (dispatch, getState) => {
const streamingAPIBaseURL = getState().getIn(['meta', 'streaming_api_base_url']);
const accessToken = getState().getIn(['meta', 'access_token']);
const { onConnect, onReceive, onDisconnect } = callbacks(dispatch, getState);
// If we cannot use a websockets connection, we must fall back
// to using individual connections for each channel
if (!streamingAPIBaseURL.startsWith('ws')) {
const connection = createConnection(streamingAPIBaseURL, accessToken, channelNameWithInlineParams(channelName, params), {
connected () {
onConnect();
},
received (data) {
onReceive(data);
},
disconnected () {
onDisconnect();
},
reconnected () {
onConnect();
},
});
return () => {
connection.close();
};
}
const subscription = {
channelName,
params,
onConnect,
onReceive,
onDisconnect,
};
addSubscription(subscription);
// If a connection is open, we can execute the subscription right now. Otherwise,
// because we have already registered it, it will be executed on connect
if (!sharedConnection) {
sharedConnection = /** @type {WebSocketClient} */ (createConnection(streamingAPIBaseURL, accessToken, '', sharedCallbacks));
} else if (sharedConnection.readyState === WebSocketClient.OPEN) {
subscribe(subscription);
}
return () => {
removeSubscription(subscription);
unsubscribe(subscription);
};
};
const KNOWN_EVENT_TYPES = [
'update',
'delete',
'notification',
'conversation',
'filters_changed',
'encrypted_message',
'announcement',
'announcement.delete',
'announcement.reaction',
];
/**
* @param {MessageEvent} e
* @param {function(StreamEvent): void} received
*/
const handleEventSourceMessage = (e, received) => {
received({
event: e.type,
payload: e.data,
});
};
/**
* @param {string} streamingAPIBaseURL
* @param {string} accessToken
* @param {string} channelName
* @param {{ connected: Function, received: function(StreamEvent): void, disconnected: Function, reconnected: Function }} callbacks
* @return {WebSocketClient | EventSource}
*/
const createConnection = (streamingAPIBaseURL, accessToken, channelName, { connected, received, disconnected, reconnected }) => {
const params = channelName.split('&');
channelName = params.shift();
if (streamingAPIBaseURL.startsWith('ws')) {
const ws = new WebSocketClient(`${streamingAPIBaseURL}/api/v1/streaming/?${params.join('&')}`, accessToken);
ws.onopen = connected;
ws.onmessage = e => received(JSON.parse(e.data));
ws.onclose = disconnected;
ws.onreconnect = reconnected;
return ws;
}
channelName = channelName.replace(/:/g, '/');
if (channelName.endsWith(':media')) {
channelName = channelName.replace('/media', '');
params.push('only_media=true');
}
params.push(`access_token=${accessToken}`);
const es = new EventSource(`${streamingAPIBaseURL}/api/v1/streaming/${channelName}?${params.join('&')}`);
es.onopen = () => {
connected();
};
KNOWN_EVENT_TYPES.forEach(type => {
es.addEventListener(type, e => handleEventSourceMessage(/** @type {MessageEvent} */ (e), received));
});
es.onerror = /** @type {function(): void} */ (disconnected);
return es;
};

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