diff options
Diffstat (limited to 'app/javascript/flavours/glitch/utils')
-rw-r--r-- | app/javascript/flavours/glitch/utils/base_polyfills.js | 47 | ||||
-rw-r--r-- | app/javascript/flavours/glitch/utils/compare_id.js | 11 | ||||
-rw-r--r-- | app/javascript/flavours/glitch/utils/extra_polyfills.js | 5 | ||||
-rw-r--r-- | app/javascript/flavours/glitch/utils/is_mobile.js | 43 | ||||
-rw-r--r-- | app/javascript/flavours/glitch/utils/load_keyboard_extensions.js | 16 | ||||
-rw-r--r-- | app/javascript/flavours/glitch/utils/load_polyfills.js | 42 | ||||
-rw-r--r-- | app/javascript/flavours/glitch/utils/main.js | 57 | ||||
-rw-r--r-- | app/javascript/flavours/glitch/utils/performance.js | 31 | ||||
-rw-r--r-- | app/javascript/flavours/glitch/utils/scroll.js | 32 | ||||
-rw-r--r-- | app/javascript/flavours/glitch/utils/settings.js | 47 | ||||
-rw-r--r-- | app/javascript/flavours/glitch/utils/stream.js | 265 | ||||
-rw-r--r-- | app/javascript/flavours/glitch/utils/uuid.js | 3 |
12 files changed, 0 insertions, 599 deletions
diff --git a/app/javascript/flavours/glitch/utils/base_polyfills.js b/app/javascript/flavours/glitch/utils/base_polyfills.js deleted file mode 100644 index 4b8123dba..000000000 --- a/app/javascript/flavours/glitch/utils/base_polyfills.js +++ /dev/null @@ -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 })); - }, - }); -} diff --git a/app/javascript/flavours/glitch/utils/compare_id.js b/app/javascript/flavours/glitch/utils/compare_id.js deleted file mode 100644 index 66cf51c4b..000000000 --- a/app/javascript/flavours/glitch/utils/compare_id.js +++ /dev/null @@ -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; - } -}; diff --git a/app/javascript/flavours/glitch/utils/extra_polyfills.js b/app/javascript/flavours/glitch/utils/extra_polyfills.js deleted file mode 100644 index 3acc55abd..000000000 --- a/app/javascript/flavours/glitch/utils/extra_polyfills.js +++ /dev/null @@ -1,5 +0,0 @@ -import 'intersection-observer'; -import 'requestidlecallback'; -import objectFitImages from 'object-fit-images'; - -objectFitImages(); diff --git a/app/javascript/flavours/glitch/utils/is_mobile.js b/app/javascript/flavours/glitch/utils/is_mobile.js deleted file mode 100644 index 0d5663098..000000000 --- a/app/javascript/flavours/glitch/utils/is_mobile.js +++ /dev/null @@ -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; diff --git a/app/javascript/flavours/glitch/utils/load_keyboard_extensions.js b/app/javascript/flavours/glitch/utils/load_keyboard_extensions.js deleted file mode 100644 index 2dd0e45fa..000000000 --- a/app/javascript/flavours/glitch/utils/load_keyboard_extensions.js +++ /dev/null @@ -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(); -} diff --git a/app/javascript/flavours/glitch/utils/load_polyfills.js b/app/javascript/flavours/glitch/utils/load_polyfills.js deleted file mode 100644 index 73eedc9dc..000000000 --- a/app/javascript/flavours/glitch/utils/load_polyfills.js +++ /dev/null @@ -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; diff --git a/app/javascript/flavours/glitch/utils/main.js b/app/javascript/flavours/glitch/utils/main.js deleted file mode 100644 index 39a46db00..000000000 --- a/app/javascript/flavours/glitch/utils/main.js +++ /dev/null @@ -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; diff --git a/app/javascript/flavours/glitch/utils/performance.js b/app/javascript/flavours/glitch/utils/performance.js deleted file mode 100644 index 450a90626..000000000 --- a/app/javascript/flavours/glitch/utils/performance.js +++ /dev/null @@ -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); - } -} diff --git a/app/javascript/flavours/glitch/utils/scroll.js b/app/javascript/flavours/glitch/utils/scroll.js deleted file mode 100644 index 84fe58269..000000000 --- a/app/javascript/flavours/glitch/utils/scroll.js +++ /dev/null @@ -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); diff --git a/app/javascript/flavours/glitch/utils/settings.js b/app/javascript/flavours/glitch/utils/settings.js deleted file mode 100644 index 7643a508e..000000000 --- a/app/javascript/flavours/glitch/utils/settings.js +++ /dev/null @@ -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'); diff --git a/app/javascript/flavours/glitch/utils/stream.js b/app/javascript/flavours/glitch/utils/stream.js deleted file mode 100644 index c6d12cd6f..000000000 --- a/app/javascript/flavours/glitch/utils/stream.js +++ /dev/null @@ -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; -}; diff --git a/app/javascript/flavours/glitch/utils/uuid.js b/app/javascript/flavours/glitch/utils/uuid.js deleted file mode 100644 index be1899305..000000000 --- a/app/javascript/flavours/glitch/utils/uuid.js +++ /dev/null @@ -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); -}; |