about summary refs log tree commit diff
path: root/app/javascript/flavours/glitch/features
diff options
context:
space:
mode:
authorClaire <claire.github-309c@sitedethib.com>2022-10-11 10:51:33 +0200
committerClaire <claire.github-309c@sitedethib.com>2022-10-11 11:31:11 +0200
commit015e798394a98bb826446ceb55bfadacc5888215 (patch)
tree2666538bc8abb01b1aaa3ec886eb566b88204051 /app/javascript/flavours/glitch/features
parentd3879c07b1b2140bd19433ae06855894228bd90f (diff)
Move some modules from flavours/glitch/utils/ back to flavours/glitch/features/ui/util/
Diffstat (limited to 'app/javascript/flavours/glitch/features')
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js2
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/language_dropdown.js2
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/options.js2
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/search.js2
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/upload.js2
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/upload_progress.js2
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/warning.js2
-rw-r--r--app/javascript/flavours/glitch/features/compose/index.js2
-rw-r--r--app/javascript/flavours/glitch/features/list_editor/index.js2
-rw-r--r--app/javascript/flavours/glitch/features/pinned_accounts_editor/index.js2
-rw-r--r--app/javascript/flavours/glitch/features/status/components/detailed_status.js2
-rw-r--r--app/javascript/flavours/glitch/features/status/index.js2
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/upload_area.js2
-rw-r--r--app/javascript/flavours/glitch/features/ui/index.js2
-rw-r--r--app/javascript/flavours/glitch/features/ui/util/fullscreen.js46
-rw-r--r--app/javascript/flavours/glitch/features/ui/util/get_rect_from_entry.js21
-rw-r--r--app/javascript/flavours/glitch/features/ui/util/intersection_observer_wrapper.js57
-rw-r--r--app/javascript/flavours/glitch/features/ui/util/optional_motion.js5
-rw-r--r--app/javascript/flavours/glitch/features/ui/util/react_router_helpers.js69
-rw-r--r--app/javascript/flavours/glitch/features/ui/util/reduced_motion.js44
-rw-r--r--app/javascript/flavours/glitch/features/ui/util/schedule_idle_task.js29
-rw-r--r--app/javascript/flavours/glitch/features/video/index.js2
22 files changed, 286 insertions, 15 deletions
diff --git a/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js b/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js
index 0cab9a8a8..21835e628 100644
--- a/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js
+++ b/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js
@@ -10,7 +10,7 @@ import Icon from 'flavours/glitch/components/icon';
 
 //  Utils.
 import { withPassive } from 'flavours/glitch/utils/dom_helpers';
-import Motion from 'flavours/glitch/utils/optional_motion';
+import Motion from '../../ui/util/optional_motion';
 import { assignHandlers } from 'flavours/glitch/utils/react_helpers';
 
 //  The spring to use with our motion.
diff --git a/app/javascript/flavours/glitch/features/compose/components/language_dropdown.js b/app/javascript/flavours/glitch/features/compose/components/language_dropdown.js
index 563a9d687..31f1d4e73 100644
--- a/app/javascript/flavours/glitch/features/compose/components/language_dropdown.js
+++ b/app/javascript/flavours/glitch/features/compose/components/language_dropdown.js
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
 import { injectIntl, defineMessages } from 'react-intl';
 import TextIconButton from './text_icon_button';
 import Overlay from 'react-overlays/lib/Overlay';
-import Motion from 'flavours/glitch/utils/optional_motion';
+import Motion from 'flavours/glitch/features/ui/util/optional_motion';
 import spring from 'react-motion/lib/spring';
 import { supportsPassiveEvents } from 'detect-passive-events';
 import classNames from 'classnames';
diff --git a/app/javascript/flavours/glitch/features/compose/components/options.js b/app/javascript/flavours/glitch/features/compose/components/options.js
index b885ae5b0..32a464011 100644
--- a/app/javascript/flavours/glitch/features/compose/components/options.js
+++ b/app/javascript/flavours/glitch/features/compose/components/options.js
@@ -16,7 +16,7 @@ import LanguageDropdown from '../containers/language_dropdown_container';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 
 //  Utils.
-import Motion from 'flavours/glitch/utils/optional_motion';
+import Motion from '../../ui/util/optional_motion';
 import { pollLimits } from 'flavours/glitch/initial_state';
 
 //  Messages.
diff --git a/app/javascript/flavours/glitch/features/compose/components/search.js b/app/javascript/flavours/glitch/features/compose/components/search.js
index 148e43260..a59418e46 100644
--- a/app/javascript/flavours/glitch/features/compose/components/search.js
+++ b/app/javascript/flavours/glitch/features/compose/components/search.js
@@ -17,7 +17,7 @@ import Icon from 'flavours/glitch/components/icon';
 //  Utils.
 import { focusRoot } from 'flavours/glitch/utils/dom_helpers';
 import { searchEnabled } from 'flavours/glitch/initial_state';
-import Motion from 'flavours/glitch/utils/optional_motion';
+import Motion from '../../ui/util/optional_motion';
 
 const messages = defineMessages({
   placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
diff --git a/app/javascript/flavours/glitch/features/compose/components/upload.js b/app/javascript/flavours/glitch/features/compose/components/upload.js
index b344dece1..b926c455d 100644
--- a/app/javascript/flavours/glitch/features/compose/components/upload.js
+++ b/app/javascript/flavours/glitch/features/compose/components/upload.js
@@ -1,7 +1,7 @@
 import React from 'react';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
-import Motion from 'flavours/glitch/utils/optional_motion';
+import Motion from '../../ui/util/optional_motion';
 import spring from 'react-motion/lib/spring';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import { FormattedMessage } from 'react-intl';
diff --git a/app/javascript/flavours/glitch/features/compose/components/upload_progress.js b/app/javascript/flavours/glitch/features/compose/components/upload_progress.js
index b5126bd4e..8896bbffd 100644
--- a/app/javascript/flavours/glitch/features/compose/components/upload_progress.js
+++ b/app/javascript/flavours/glitch/features/compose/components/upload_progress.js
@@ -1,6 +1,6 @@
 import React from 'react';
 import PropTypes from 'prop-types';
-import Motion from 'flavours/glitch/utils/optional_motion';
+import Motion from '../../ui/util/optional_motion';
 import spring from 'react-motion/lib/spring';
 import Icon from 'flavours/glitch/components/icon';
 
diff --git a/app/javascript/flavours/glitch/features/compose/components/warning.js b/app/javascript/flavours/glitch/features/compose/components/warning.js
index 3bcf9a7f7..4009be8c6 100644
--- a/app/javascript/flavours/glitch/features/compose/components/warning.js
+++ b/app/javascript/flavours/glitch/features/compose/components/warning.js
@@ -1,6 +1,6 @@
 import React from 'react';
 import PropTypes from 'prop-types';
-import Motion from 'flavours/glitch/utils/optional_motion';
+import Motion from '../../ui/util/optional_motion';
 import spring from 'react-motion/lib/spring';
 
 export default class Warning extends React.PureComponent {
diff --git a/app/javascript/flavours/glitch/features/compose/index.js b/app/javascript/flavours/glitch/features/compose/index.js
index 7c723b048..567bb3711 100644
--- a/app/javascript/flavours/glitch/features/compose/index.js
+++ b/app/javascript/flavours/glitch/features/compose/index.js
@@ -8,7 +8,7 @@ import { mountCompose, unmountCompose } from 'flavours/glitch/actions/compose';
 import { injectIntl, defineMessages } from 'react-intl';
 import classNames from 'classnames';
 import SearchContainer from './containers/search_container';
-import Motion from 'flavours/glitch/utils/optional_motion';
+import Motion from '../ui/util/optional_motion';
 import spring from 'react-motion/lib/spring';
 import SearchResultsContainer from './containers/search_results_container';
 import { me, mascot } from 'flavours/glitch/initial_state';
diff --git a/app/javascript/flavours/glitch/features/list_editor/index.js b/app/javascript/flavours/glitch/features/list_editor/index.js
index 505b07cfc..c2ca07053 100644
--- a/app/javascript/flavours/glitch/features/list_editor/index.js
+++ b/app/javascript/flavours/glitch/features/list_editor/index.js
@@ -8,7 +8,7 @@ import { setupListEditor, clearListSuggestions, resetListEditor } from 'flavours
 import AccountContainer from './containers/account_container';
 import SearchContainer from './containers/search_container';
 import EditListForm from './components/edit_list_form';
-import Motion from 'flavours/glitch/utils/optional_motion';
+import Motion from '../ui/util/optional_motion';
 import spring from 'react-motion/lib/spring';
 
 const mapStateToProps = state => ({
diff --git a/app/javascript/flavours/glitch/features/pinned_accounts_editor/index.js b/app/javascript/flavours/glitch/features/pinned_accounts_editor/index.js
index 30fc0b6f9..43ae0ec2f 100644
--- a/app/javascript/flavours/glitch/features/pinned_accounts_editor/index.js
+++ b/app/javascript/flavours/glitch/features/pinned_accounts_editor/index.js
@@ -7,7 +7,7 @@ import { injectIntl, FormattedMessage } from 'react-intl';
 import { fetchPinnedAccounts, clearPinnedAccountsSuggestions, resetPinnedAccountsEditor } from 'flavours/glitch/actions/accounts';
 import AccountContainer from './containers/account_container';
 import SearchContainer from './containers/search_container';
-import Motion from 'flavours/glitch/utils/optional_motion';
+import Motion from 'flavours/glitch/features/ui/util/optional_motion';
 import spring from 'react-motion/lib/spring';
 
 const mapStateToProps = state => ({
diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.js b/app/javascript/flavours/glitch/features/status/components/detailed_status.js
index ec03e6ba0..46770930f 100644
--- a/app/javascript/flavours/glitch/features/status/components/detailed_status.js
+++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.js
@@ -13,7 +13,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
 import Video from 'flavours/glitch/features/video';
 import Audio from 'flavours/glitch/features/audio';
 import VisibilityIcon from 'flavours/glitch/components/status_visibility_icon';
-import scheduleIdleTask from 'flavours/glitch/utils/schedule_idle_task';
+import scheduleIdleTask from '../../ui/util/schedule_idle_task';
 import classNames from 'classnames';
 import PollContainer from 'flavours/glitch/containers/poll_container';
 import Icon from 'flavours/glitch/components/icon';
diff --git a/app/javascript/flavours/glitch/features/status/index.js b/app/javascript/flavours/glitch/features/status/index.js
index 5ce2aab02..c967ef34d 100644
--- a/app/javascript/flavours/glitch/features/status/index.js
+++ b/app/javascript/flavours/glitch/features/status/index.js
@@ -48,7 +48,7 @@ import { defineMessages, injectIntl } from 'react-intl';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import { HotKeys } from 'react-hotkeys';
 import { boostModal, favouriteModal, deleteModal, title } from 'flavours/glitch/initial_state';
-import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from 'flavours/glitch/utils/fullscreen';
+import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../ui/util/fullscreen';
 import { autoUnfoldCW } from 'flavours/glitch/utils/content_warning';
 import { textForScreenReader, defaultMediaVisibility } from 'flavours/glitch/components/status';
 import Icon from 'flavours/glitch/components/icon';
diff --git a/app/javascript/flavours/glitch/features/ui/components/upload_area.js b/app/javascript/flavours/glitch/features/ui/components/upload_area.js
index 0f8f4535c..6958ba9df 100644
--- a/app/javascript/flavours/glitch/features/ui/components/upload_area.js
+++ b/app/javascript/flavours/glitch/features/ui/components/upload_area.js
@@ -1,6 +1,6 @@
 import React from 'react';
 import PropTypes from 'prop-types';
-import Motion from 'flavours/glitch/utils/optional_motion';
+import Motion from '../../ui/util/optional_motion';
 import spring from 'react-motion/lib/spring';
 import { FormattedMessage } from 'react-intl';
 
diff --git a/app/javascript/flavours/glitch/features/ui/index.js b/app/javascript/flavours/glitch/features/ui/index.js
index f8e868790..f77e89591 100644
--- a/app/javascript/flavours/glitch/features/ui/index.js
+++ b/app/javascript/flavours/glitch/features/ui/index.js
@@ -14,7 +14,7 @@ import { fetchServer } from 'flavours/glitch/actions/server';
 import { clearHeight } from 'flavours/glitch/actions/height_cache';
 import { changeLayout } from 'flavours/glitch/actions/app';
 import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'flavours/glitch/actions/markers';
-import { WrappedSwitch, WrappedRoute } from 'flavours/glitch/utils/react_router_helpers';
+import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers';
 import UploadArea from './components/upload_area';
 import PermaLink from 'flavours/glitch/components/permalink';
 import ColumnsAreaContainer from './containers/columns_area_container';
diff --git a/app/javascript/flavours/glitch/features/ui/util/fullscreen.js b/app/javascript/flavours/glitch/features/ui/util/fullscreen.js
new file mode 100644
index 000000000..cf5d0cf98
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/ui/util/fullscreen.js
@@ -0,0 +1,46 @@
+// APIs for normalizing fullscreen operations. Note that Edge uses
+// the WebKit-prefixed APIs currently (as of Edge 16).
+
+export const isFullscreen = () => document.fullscreenElement ||
+  document.webkitFullscreenElement ||
+  document.mozFullScreenElement;
+
+export const exitFullscreen = () => {
+  if (document.exitFullscreen) {
+    document.exitFullscreen();
+  } else if (document.webkitExitFullscreen) {
+    document.webkitExitFullscreen();
+  } else if (document.mozCancelFullScreen) {
+    document.mozCancelFullScreen();
+  }
+};
+
+export const requestFullscreen = el => {
+  if (el.requestFullscreen) {
+    el.requestFullscreen();
+  } else if (el.webkitRequestFullscreen) {
+    el.webkitRequestFullscreen();
+  } else if (el.mozRequestFullScreen) {
+    el.mozRequestFullScreen();
+  }
+};
+
+export const attachFullscreenListener = (listener) => {
+  if ('onfullscreenchange' in document) {
+    document.addEventListener('fullscreenchange', listener);
+  } else if ('onwebkitfullscreenchange' in document) {
+    document.addEventListener('webkitfullscreenchange', listener);
+  } else if ('onmozfullscreenchange' in document) {
+    document.addEventListener('mozfullscreenchange', listener);
+  }
+};
+
+export const detachFullscreenListener = (listener) => {
+  if ('onfullscreenchange' in document) {
+    document.removeEventListener('fullscreenchange', listener);
+  } else if ('onwebkitfullscreenchange' in document) {
+    document.removeEventListener('webkitfullscreenchange', listener);
+  } else if ('onmozfullscreenchange' in document) {
+    document.removeEventListener('mozfullscreenchange', listener);
+  }
+};
diff --git a/app/javascript/flavours/glitch/features/ui/util/get_rect_from_entry.js b/app/javascript/flavours/glitch/features/ui/util/get_rect_from_entry.js
new file mode 100644
index 000000000..c266cd7dc
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/ui/util/get_rect_from_entry.js
@@ -0,0 +1,21 @@
+
+// Get the bounding client rect from an IntersectionObserver entry.
+// This is to work around a bug in Chrome: https://crbug.com/737228
+
+let hasBoundingRectBug;
+
+function getRectFromEntry(entry) {
+  if (typeof hasBoundingRectBug !== 'boolean') {
+    const boundingRect = entry.target.getBoundingClientRect();
+    const observerRect = entry.boundingClientRect;
+    hasBoundingRectBug = boundingRect.height !== observerRect.height ||
+      boundingRect.top !== observerRect.top ||
+      boundingRect.width !== observerRect.width ||
+      boundingRect.bottom !== observerRect.bottom ||
+      boundingRect.left !== observerRect.left ||
+      boundingRect.right !== observerRect.right;
+  }
+  return hasBoundingRectBug ? entry.target.getBoundingClientRect() : entry.boundingClientRect;
+}
+
+export default getRectFromEntry;
diff --git a/app/javascript/flavours/glitch/features/ui/util/intersection_observer_wrapper.js b/app/javascript/flavours/glitch/features/ui/util/intersection_observer_wrapper.js
new file mode 100644
index 000000000..2b24c6583
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/ui/util/intersection_observer_wrapper.js
@@ -0,0 +1,57 @@
+// Wrapper for IntersectionObserver in order to make working with it
+// a bit easier. We also follow this performance advice:
+// "If you need to observe multiple elements, it is both possible and
+// advised to observe multiple elements using the same IntersectionObserver
+// instance by calling observe() multiple times."
+// https://developers.google.com/web/updates/2016/04/intersectionobserver
+
+class IntersectionObserverWrapper {
+
+  callbacks = {};
+  observerBacklog = [];
+  observer = null;
+
+  connect (options) {
+    const onIntersection = (entries) => {
+      entries.forEach(entry => {
+        const id = entry.target.getAttribute('data-id');
+        if (this.callbacks[id]) {
+          this.callbacks[id](entry);
+        }
+      });
+    };
+
+    this.observer = new IntersectionObserver(onIntersection, options);
+    this.observerBacklog.forEach(([ id, node, callback ]) => {
+      this.observe(id, node, callback);
+    });
+    this.observerBacklog = null;
+  }
+
+  observe (id, node, callback) {
+    if (!this.observer) {
+      this.observerBacklog.push([ id, node, callback ]);
+    } else {
+      this.callbacks[id] = callback;
+      this.observer.observe(node);
+    }
+  }
+
+  unobserve (id, node) {
+    if (this.observer) {
+      delete this.callbacks[id];
+      this.observer.unobserve(node);
+    }
+  }
+
+  disconnect () {
+    if (this.observer) {
+      this.callbacks = {};
+      this.observer.disconnect();
+      this.observer = null;
+    }
+  }
+
+}
+
+export default IntersectionObserverWrapper;
diff --git a/app/javascript/flavours/glitch/features/ui/util/optional_motion.js b/app/javascript/flavours/glitch/features/ui/util/optional_motion.js
new file mode 100644
index 000000000..a7fbe6310
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/ui/util/optional_motion.js
@@ -0,0 +1,5 @@
+import { reduceMotion } from 'flavours/glitch/initial_state';
+import ReducedMotion from './reduced_motion';
+import Motion from 'react-motion/lib/Motion';
+
+export default reduceMotion ? ReducedMotion : Motion;
diff --git a/app/javascript/flavours/glitch/features/ui/util/react_router_helpers.js b/app/javascript/flavours/glitch/features/ui/util/react_router_helpers.js
new file mode 100644
index 000000000..e36c512f3
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/ui/util/react_router_helpers.js
@@ -0,0 +1,69 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Switch, Route } from 'react-router-dom';
+
+import ColumnLoading from 'flavours/glitch/features/ui/components/column_loading';
+import BundleColumnError from 'flavours/glitch/features/ui/components/bundle_column_error';
+import BundleContainer from 'flavours/glitch/features/ui/containers/bundle_container';
+
+// Small wrapper to pass multiColumn to the route components
+export class WrappedSwitch extends React.PureComponent {
+
+  render () {
+    const { multiColumn, children } = this.props;
+
+    return (
+      <Switch>
+        {React.Children.map(children, child => React.cloneElement(child, { multiColumn }))}
+      </Switch>
+    );
+  }
+
+}
+
+WrappedSwitch.propTypes = {
+  multiColumn: PropTypes.bool,
+  children: PropTypes.node,
+};
+
+// Small Wraper to extract the params from the route and pass
+// them to the rendered component, together with the content to
+// be rendered inside (the children)
+export class WrappedRoute extends React.Component {
+
+  static propTypes = {
+    component: PropTypes.func.isRequired,
+    content: PropTypes.node,
+    multiColumn: PropTypes.bool,
+    componentParams: PropTypes.object,
+  }
+
+  static defaultProps = {
+    componentParams: {},
+  };
+
+  renderComponent = ({ match }) => {
+    const { component, content, multiColumn, componentParams } = this.props;
+
+    return (
+      <BundleContainer fetchComponent={component} loading={this.renderLoading} error={this.renderError}>
+        {Component => <Component params={match.params} multiColumn={multiColumn} {...componentParams}>{content}</Component>}
+      </BundleContainer>
+    );
+  }
+
+  renderLoading = () => {
+    return <ColumnLoading />;
+  }
+
+  renderError = (props) => {
+    return <BundleColumnError {...props} />;
+  }
+
+  render () {
+    const { component: Component, content, ...rest } = this.props;
+
+    return <Route {...rest} render={this.renderComponent} />;
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/features/ui/util/reduced_motion.js b/app/javascript/flavours/glitch/features/ui/util/reduced_motion.js
new file mode 100644
index 000000000..95519042b
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/ui/util/reduced_motion.js
@@ -0,0 +1,44 @@
+// Like react-motion's Motion, but reduces all animations to cross-fades
+// for the benefit of users with motion sickness.
+import React from 'react';
+import Motion from 'react-motion/lib/Motion';
+import PropTypes from 'prop-types';
+
+const stylesToKeep = ['opacity', 'backgroundOpacity'];
+
+const extractValue = (value) => {
+  // This is either an object with a "val" property or it's a number
+  return (typeof value === 'object' && value && 'val' in value) ? value.val : value;
+};
+
+class ReducedMotion extends React.Component {
+
+  static propTypes = {
+    defaultStyle: PropTypes.object,
+    style: PropTypes.object,
+    children: PropTypes.func,
+  }
+
+  render() {
+
+    const { style, defaultStyle, children } = this.props;
+
+    Object.keys(style).forEach(key => {
+      if (stylesToKeep.includes(key)) {
+        return;
+      }
+      // If it's setting an x or height or scale or some other value, we need
+      // to preserve the end-state value without actually animating it
+      style[key] = defaultStyle[key] = extractValue(style[key]);
+    });
+
+    return (
+      <Motion style={style} defaultStyle={defaultStyle}>
+        {children}
+      </Motion>
+    );
+  }
+
+}
+
+export default ReducedMotion;
diff --git a/app/javascript/flavours/glitch/features/ui/util/schedule_idle_task.js b/app/javascript/flavours/glitch/features/ui/util/schedule_idle_task.js
new file mode 100644
index 000000000..b04d4a8ee
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/ui/util/schedule_idle_task.js
@@ -0,0 +1,29 @@
+// Wrapper to call requestIdleCallback() to schedule low-priority work.
+// See https://developer.mozilla.org/en-US/docs/Web/API/Background_Tasks_API
+// for a good breakdown of the concepts behind this.
+
+import Queue from 'tiny-queue';
+
+const taskQueue = new Queue();
+let runningRequestIdleCallback = false;
+
+function runTasks(deadline) {
+  while (taskQueue.length && deadline.timeRemaining() > 0) {
+    taskQueue.shift()();
+  }
+  if (taskQueue.length) {
+    requestIdleCallback(runTasks);
+  } else {
+    runningRequestIdleCallback = false;
+  }
+}
+
+function scheduleIdleTask(task) {
+  taskQueue.push(task);
+  if (!runningRequestIdleCallback) {
+    runningRequestIdleCallback = true;
+    requestIdleCallback(runTasks);
+  }
+}
+
+export default scheduleIdleTask;
diff --git a/app/javascript/flavours/glitch/features/video/index.js b/app/javascript/flavours/glitch/features/video/index.js
index 0a37cff14..cb4655f7f 100644
--- a/app/javascript/flavours/glitch/features/video/index.js
+++ b/app/javascript/flavours/glitch/features/video/index.js
@@ -4,7 +4,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import { is } from 'immutable';
 import { throttle, debounce } from 'lodash';
 import classNames from 'classnames';
-import { isFullscreen, requestFullscreen, exitFullscreen } from 'flavours/glitch/utils/fullscreen';
+import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';
 import { displayMedia, useBlurhash } from 'flavours/glitch/initial_state';
 import Icon from 'flavours/glitch/components/icon';
 import Blurhash from 'flavours/glitch/components/blurhash';