~cytrogen/masto-fe

d640c0356ebda19e1940c8c100613881243dc4b2 — Cytrogen 14 days ago bc69d48
Fix merged PR bugs and rewrite reduced-motion to pure CSS

- PR #64: fix typo "ad" → "add" in bookmark folder empty message
- PR #64: fix reducer using wrong key ('folder'/'title' → 'name')
- PR #84: implement confirm_unfollow using local_settings instead of
  server-side unfollowModal global
- PR #104: replace JS body-class approach with native CSS
  @media (prefers-reduced-motion) queries across all SCSS files
- Wide view: columns fill available space, content centered at 600px
M app/javascript/flavours/glitch/containers/account_container.jsx => app/javascript/flavours/glitch/containers/account_container.jsx +18 -15
@@ 13,7 13,7 @@ import {
import { openModal } from "flavours/glitch/actions/modal";
import { initMuteModal } from "flavours/glitch/actions/mutes";
import Account from "flavours/glitch/components/account";
import { unfollowModal } from "flavours/glitch/initial_state";

import { makeGetAccount } from "flavours/glitch/selectors";

const messages = defineMessages({


@@ 33,22 33,25 @@ const makeMapStateToProps = () => {
const mapDispatchToProps = (dispatch, { intl }) => ({

  onFollow (account) {
    if (account.getIn(["relationship", "following"]) || account.getIn(["relationship", "requested"])) {
      if (unfollowModal) {
        dispatch(openModal({
          modalType: "CONFIRM",
          modalProps: {
            message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get("acct")}</strong> }} />,
            confirm: intl.formatMessage(messages.unfollowConfirm),
            onConfirm: () => dispatch(unfollowAccount(account.get("id"))),
          },
        }));
    dispatch((_, getState) => {
      const state = getState();
      if (account.getIn(["relationship", "following"]) || account.getIn(["relationship", "requested"])) {
        if (state.getIn(["local_settings", "confirm_unfollow"])) {
          dispatch(openModal({
            modalType: "CONFIRM",
            modalProps: {
              message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get("acct")}</strong> }} />,
              confirm: intl.formatMessage(messages.unfollowConfirm),
              onConfirm: () => dispatch(unfollowAccount(account.get("id"))),
            },
          }));
        } else {
          dispatch(unfollowAccount(account.get("id")));
        }
      } else {
        dispatch(unfollowAccount(account.get("id")));
        dispatch(followAccount(account.get("id")));
      }
    } else {
      dispatch(followAccount(account.get("id")));
    }
    });
  },

  onBlock (account) {

M app/javascript/flavours/glitch/features/bookmark_folder/index.jsx => app/javascript/flavours/glitch/features/bookmark_folder/index.jsx +1 -1
@@ 88,7 88,7 @@ class BookmarkFolder extends ImmutablePureComponent {
    const pinned = !!columnId;
    const name = folder ? folder.get('name') : folderId;

    const emptyMessage = <FormattedMessage id='empty_column.bookmarked_statuses.folder' defaultMessage="You don't have any bookmarked posts in this folder yet. When you ad one, it will show up here." />;
    const emptyMessage = <FormattedMessage id='empty_column.bookmarked_statuses.folder' defaultMessage="You don't have any bookmarked posts in this folder yet. When you add one, it will show up here." />;
    
    if (typeof folder === 'undefined') {
      return (

M app/javascript/flavours/glitch/main.jsx => app/javascript/flavours/glitch/main.jsx +0 -2
@@ 5,7 5,6 @@ import Mastodon from "flavours/glitch/containers/mastodon";
import { me } from "flavours/glitch/initial_state";
import * as perf from "flavours/glitch/performance";
import ready from "flavours/glitch/ready";
import { setReducedMotionBodyClass } from "flavours/glitch/utils/accessibility";
import { store } from "flavours/glitch/store";

/**


@@ 13,7 12,6 @@ import { store } from "flavours/glitch/store";
 */
function main() {
  perf.start("main()");
  setReducedMotionBodyClass();

  return ready(async () => {
    const mountNode = document.getElementById("mastodon");

M app/javascript/flavours/glitch/reducers/bookmark_folder_editor.js => app/javascript/flavours/glitch/reducers/bookmark_folder_editor.js +1 -1
@@ 26,7 26,7 @@ export default function listEditorReducer(state = initialState, action) {
  case BOOKMARK_FOLDER_EDITOR_SETUP:
    return state.withMutations(map => {
      map.set('folderId', action.folder.get('id'));
      map.set('folder', action.folder.get('title'));
      map.set('name', action.folder.get('name'));
    });
  case BOOKMARK_FOLDER_EDITOR_NAME_CHANGE:
    return state.withMutations(map => {

M app/javascript/flavours/glitch/styles/components/columns.scss => app/javascript/flavours/glitch/styles/components/columns.scss +1 -2
@@ 393,9 393,8 @@ $ui-header-height: 55px;
  overflow: hidden;

  .wide .columns-area:not(.columns-area--mobile) & {
    flex: auto;
    flex: 1 1 auto;
    min-width: 330px;
    max-width: 400px;
  }

  > .scrollable {

M app/javascript/flavours/glitch/styles/components/compose_form.scss => app/javascript/flavours/glitch/styles/components/compose_form.scss +6 -4
@@ 25,10 25,12 @@
  }
}

.no-reduce-motion .spoiler-input {
  transition:
    height 0.4s ease,
    opacity 0.4s ease;
@media (prefers-reduced-motion: no-preference) {
  .spoiler-input {
    transition:
      height 0.4s ease,
      opacity 0.4s ease;
  }
}

.spoiler-input {

M app/javascript/flavours/glitch/styles/components/media.scss => app/javascript/flavours/glitch/styles/components/media.scss +3 -3
@@ 643,7 643,7 @@
    position: relative;
    overflow: hidden;

    .no-reduce-motion & {
    @media (prefers-reduced-motion: no-preference) {
      transition: all 100ms linear;
    }



@@ 691,7 691,7 @@
      box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2);
      opacity: 0;

      .no-reduce-motion & {
      @media (prefers-reduced-motion: no-preference) {
        transition: opacity 100ms linear;
      }
    }


@@ 760,7 760,7 @@
      background: lighten($ui-highlight-color, 8%);
      box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2);

      .no-reduce-motion & {
      @media (prefers-reduced-motion: no-preference) {
        transition: opacity 0.1s ease;
      }


M app/javascript/flavours/glitch/styles/components/misc.scss => app/javascript/flavours/glitch/styles/components/misc.scss +19 -15
@@ 365,16 365,18 @@ body > [data-popper-placement] {
  color: $red-bookmark;
}

.no-reduce-motion .icon-button.star-icon {
  &.activate {
    & > .fa-star {
      animation: spring-rotate-in 1s linear;
@media (prefers-reduced-motion: no-preference) {
  .icon-button.star-icon {
    &.activate {
      & > .fa-star {
        animation: spring-rotate-in 1s linear;
      }
    }
  }

  &.deactivate {
    & > .fa-star {
      animation: spring-rotate-out 1s linear;
    &.deactivate {
      & > .fa-star {
        animation: spring-rotate-out 1s linear;
      }
    }
  }
}


@@ 518,7 520,7 @@ body > [data-popper-placement] {
    }
  }

  .reduce-motion & {
  @media (prefers-reduced-motion: reduce) {
    animation: none;
  }
}


@@ 1086,13 1088,15 @@ button.icon-button.active i.fa-retweet {
  background-position: 0 100%;
}

.reduce-motion button.icon-button i.fa-retweet,
.reduce-motion button.icon-button.active i.fa-retweet {
  transition: none;
}
@media (prefers-reduced-motion: reduce) {
  button.icon-button i.fa-retweet,
  button.icon-button.active i.fa-retweet {
    transition: none;
  }

.reduce-motion button.icon-button.disabled i.fa-retweet {
  color: darken($action-button-color, 13%);
  button.icon-button.disabled i.fa-retweet {
    color: darken($action-button-color, 13%);
  }
}

.load-more {

M app/javascript/flavours/glitch/styles/components/status.scss => app/javascript/flavours/glitch/styles/components/status.scss +25 -8
@@ 381,6 381,21 @@
  }
}

// Wide mode: constrain content width for readability,
// columns fill available space but content stays centered.
.wide .columns-area:not(.columns-area--mobile) {
  .status,
  .detailed-status,
  .detailed-status__action-bar,
  .notification-favourite,
  .notification-follow,
  .notification-follow-request,
  .status__wrapper--filtered {
    max-width: 600px;
    margin-inline: auto;
  }
}

.notification-favourite {
  .status.status-direct {
    background: transparent;


@@ 445,16 460,18 @@
  }
}

.no-reduce-motion .status__collapse-button {
  &.activate {
    & > .fa-angle-double-up {
      animation: spring-flip-in 1s linear;
@media (prefers-reduced-motion: no-preference) {
  .status__collapse-button {
    &.activate {
      & > .fa-angle-double-up {
        animation: spring-flip-in 1s linear;
      }
    }
  }

  &.deactivate {
    & > .fa-angle-double-up {
      animation: spring-flip-out 1s linear;
    &.deactivate {
      & > .fa-angle-double-up {
        animation: spring-flip-out 1s linear;
      }
    }
  }
}

D app/javascript/flavours/glitch/utils/accessibility.js => app/javascript/flavours/glitch/utils/accessibility.js +0 -26
@@ 1,26 0,0 @@
import ready from "../ready";

ready(() => {
  setReducedMotionBodyClass();
});

export function setMediaQueryBodyClass(query, className) {
  if (query.matches) {
    document.body.classList.add(className.true);
    document.body.classList.remove(className.false);
  } else {
    document.body.classList.add(className.false);
    document.body.classList.remove(className.true);
  }
}

export function setReducedMotionBodyClass() {
  const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)");
  const className = {
    true: "reduce-motion",
    false: "no-reduce-motion",
  };

  setMediaQueryBodyClass(prefersReducedMotion, className);
  prefersReducedMotion.addEventListener("change", () => setReducedMotionBodyClass(prefersReducedMotion));
}

M app/javascript/styles/mastodon/components.scss => app/javascript/styles/mastodon/components.scss +37 -27
@@ 15,7 15,7 @@
  color: $valid-value-color;
  font-weight: 400;

  .no-reduce-motion & {
  @media (prefers-reduced-motion: no-preference) {
    transition: opacity 200ms ease;
  }
}


@@ 717,10 717,12 @@ body > [data-popper-placement] {
  }
}

.no-reduce-motion .spoiler-input {
  transition:
    height 0.4s ease,
    opacity 0.4s ease;
@media (prefers-reduced-motion: no-preference) {
  .spoiler-input {
    transition:
      height 0.4s ease,
      opacity 0.4s ease;
  }
}

.sign-in-banner {


@@ 1776,16 1778,18 @@ a.account__display-name {
  color: $red-bookmark;
}

.no-reduce-motion .icon-button.star-icon {
  &.activate {
    & > .fa-star {
      animation: spring-rotate-in 1s linear;
@media (prefers-reduced-motion: no-preference) {
  .icon-button.star-icon {
    &.activate {
      & > .fa-star {
        animation: spring-rotate-in 1s linear;
      }
    }
  }

  &.deactivate {
    & > .fa-star {
      animation: spring-rotate-out 1s linear;
    &.deactivate {
      & > .fa-star {
        animation: spring-rotate-out 1s linear;
      }
    }
  }
}


@@ 1946,7 1950,7 @@ a.account__display-name {
    }
  }

  .reduce-motion & {
  @media (prefers-reduced-motion: reduce) {
    animation: none;
  }
}


@@ 3525,9 3529,11 @@ button.icon-button.active i.fa-retweet {
  background-position: 0 100%;
}

.reduce-motion button.icon-button i.fa-retweet,
.reduce-motion button.icon-button.active i.fa-retweet {
  transition: none;
@media (prefers-reduced-motion: reduce) {
  button.icon-button i.fa-retweet,
  button.icon-button.active i.fa-retweet {
    transition: none;
  }
}

.status-card {


@@ 4592,9 4598,11 @@ a.status-card {
  }
}

.no-reduce-motion .pulse-loading {
  transform-origin: center center;
  animation: heartbeat 1.5s ease-in-out infinite both;
@media (prefers-reduced-motion: no-preference) {
  .pulse-loading {
    transform-origin: center center;
    animation: heartbeat 1.5s ease-in-out infinite both;
  }
}

@keyframes shake-bottom {


@@ 4629,9 4637,11 @@ a.status-card {
  }
}

.no-reduce-motion .shake-bottom {
  transform-origin: 50% 100%;
  animation: shake-bottom 0.8s cubic-bezier(0.455, 0.03, 0.515, 0.955) 2s 2 both;
@media (prefers-reduced-motion: no-preference) {
  .shake-bottom {
    transform-origin: 50% 100%;
    animation: shake-bottom 0.8s cubic-bezier(0.455, 0.03, 0.515, 0.955) 2s 2 both;
  }
}

.emoji-picker-dropdown__menu {


@@ 6641,7 6651,7 @@ a.status-card {
    position: relative;
    overflow: hidden;

    .no-reduce-motion & {
    @media (prefers-reduced-motion: no-preference) {
      transition: all 100ms linear;
    }



@@ 6689,7 6699,7 @@ a.status-card {
      box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2);
      opacity: 0;

      .no-reduce-motion & {
      @media (prefers-reduced-motion: no-preference) {
        transition: opacity 100ms linear;
      }
    }


@@ 6758,7 6768,7 @@ a.status-card {
      background: lighten($ui-highlight-color, 8%);
      box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2);

      .no-reduce-motion & {
      @media (prefers-reduced-motion: no-preference) {
        transition: opacity 0.1s ease;
      }



@@ 9298,7 9308,7 @@ noscript {
    inset-inline-start: 1rem;
  }

  .no-reduce-motion & {
  @media (prefers-reduced-motion: no-preference) {
    transition: 0.5s cubic-bezier(0.89, 0.01, 0.5, 1.1);
    transform: translateZ(0);
  }