From e08b31a70624fe069d2f26bf2078a69e2d48f6aa Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 7 May 2021 14:33:19 +0200 Subject: [Glitch] Add joined date to profiles in web UI Port 2c77d97e0d59e045a9b04fccc83f0f24d190d8d8 to glitch-soc Signed-off-by: Claire --- .../flavours/glitch/styles/components/accounts.scss | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'app/javascript/flavours/glitch/styles/components') diff --git a/app/javascript/flavours/glitch/styles/components/accounts.scss b/app/javascript/flavours/glitch/styles/components/accounts.scss index 6dcfbcb02..6a629353d 100644 --- a/app/javascript/flavours/glitch/styles/components/accounts.scss +++ b/app/javascript/flavours/glitch/styles/components/accounts.scss @@ -555,6 +555,17 @@ color: $primary-text-color; } + .account__header__joined { + font-size: 14px; + padding: 5px 15px; + color: $darker-text-color; + + .columns-area--mobile & { + padding-left: 20px; + padding-right: 20px; + } + } + .account__header__fields { margin: 0; border-top: 1px solid lighten($ui-base-color, 12%); -- cgit From 89bc4dff6f85e585713e777b4a860df77db2936e Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 19 Apr 2021 14:45:15 +0200 Subject: [Glitch] Change onboarding by replacing tutorial with follow recommendations in web UI Port bf903dc51070f952ab64e43918e803fdaaa15e4d to glitch-soc Signed-off-by: Claire --- .../flavours/glitch/actions/importer/normalizer.js | 1 + .../flavours/glitch/actions/suggestions.js | 7 +- app/javascript/flavours/glitch/components/logo.js | 9 ++ .../follow_recommendations/components/account.js | 85 +++++++++++++++++++ .../features/follow_recommendations/index.js | 95 ++++++++++++++++++++++ .../flavours/glitch/features/ui/index.js | 11 +++ .../glitch/styles/components/accounts.scss | 23 ++++++ .../flavours/glitch/styles/components/columns.scss | 43 ++++++++++ .../flavours/glitch/util/async-components.js | 4 + 9 files changed, 277 insertions(+), 1 deletion(-) create mode 100644 app/javascript/flavours/glitch/components/logo.js create mode 100644 app/javascript/flavours/glitch/features/follow_recommendations/components/account.js create mode 100644 app/javascript/flavours/glitch/features/follow_recommendations/index.js (limited to 'app/javascript/flavours/glitch/styles/components') diff --git a/app/javascript/flavours/glitch/actions/importer/normalizer.js b/app/javascript/flavours/glitch/actions/importer/normalizer.js index 05955963c..9b3bd0d56 100644 --- a/app/javascript/flavours/glitch/actions/importer/normalizer.js +++ b/app/javascript/flavours/glitch/actions/importer/normalizer.js @@ -24,6 +24,7 @@ export function normalizeAccount(account) { account.display_name_html = emojify(escapeTextContentForBrowser(displayName), emojiMap); account.note_emojified = emojify(account.note, emojiMap); + account.note_plain = unescapeHTML(account.note); if (account.fields) { account.fields = account.fields.map(pair => ({ diff --git a/app/javascript/flavours/glitch/actions/suggestions.js b/app/javascript/flavours/glitch/actions/suggestions.js index e867bccf7..b8ce5825c 100644 --- a/app/javascript/flavours/glitch/actions/suggestions.js +++ b/app/javascript/flavours/glitch/actions/suggestions.js @@ -1,5 +1,6 @@ import api from 'flavours/glitch/util/api'; import { importFetchedAccounts } from './importer'; +import { fetchRelationships } from './accounts'; export const SUGGESTIONS_FETCH_REQUEST = 'SUGGESTIONS_FETCH_REQUEST'; export const SUGGESTIONS_FETCH_SUCCESS = 'SUGGESTIONS_FETCH_SUCCESS'; @@ -7,13 +8,17 @@ export const SUGGESTIONS_FETCH_FAIL = 'SUGGESTIONS_FETCH_FAIL'; export const SUGGESTIONS_DISMISS = 'SUGGESTIONS_DISMISS'; -export function fetchSuggestions() { +export function fetchSuggestions(withRelationships = false) { return (dispatch, getState) => { dispatch(fetchSuggestionsRequest()); api(getState).get('/api/v2/suggestions').then(response => { dispatch(importFetchedAccounts(response.data.map(x => x.account))); dispatch(fetchSuggestionsSuccess(response.data)); + + if (withRelationships) { + dispatch(fetchRelationships(response.data.map(item => item.account.id))); + } }).catch(error => dispatch(fetchSuggestionsFail(error))); }; }; diff --git a/app/javascript/flavours/glitch/components/logo.js b/app/javascript/flavours/glitch/components/logo.js new file mode 100644 index 000000000..d1c7f08a9 --- /dev/null +++ b/app/javascript/flavours/glitch/components/logo.js @@ -0,0 +1,9 @@ +import React from 'react'; + +const Logo = () => ( + + + +); + +export default Logo; diff --git a/app/javascript/flavours/glitch/features/follow_recommendations/components/account.js b/app/javascript/flavours/glitch/features/follow_recommendations/components/account.js new file mode 100644 index 000000000..bee9f9384 --- /dev/null +++ b/app/javascript/flavours/glitch/features/follow_recommendations/components/account.js @@ -0,0 +1,85 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import { connect } from 'react-redux'; +import { makeGetAccount } from 'flavours/glitch/selectors'; +import Avatar from 'flavours/glitch/components/avatar'; +import DisplayName from 'flavours/glitch/components/display_name'; +import Permalink from 'flavours/glitch/components/permalink'; +import IconButton from 'flavours/glitch/components/icon_button'; +import { injectIntl, defineMessages } from 'react-intl'; +import { followAccount, unfollowAccount } from 'flavours/glitch/actions/accounts'; + +const messages = defineMessages({ + follow: { id: 'account.follow', defaultMessage: 'Follow' }, + unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, +}); + +const makeMapStateToProps = () => { + const getAccount = makeGetAccount(); + + const mapStateToProps = (state, props) => ({ + account: getAccount(state, props.id), + }); + + return mapStateToProps; +}; + +const getFirstSentence = str => { + const arr = str.split(/(([\.\?!]+\s)|[.。?!])/); + + return arr[0]; +}; + +export default @connect(makeMapStateToProps) +@injectIntl +class Account extends ImmutablePureComponent { + + static propTypes = { + account: ImmutablePropTypes.map.isRequired, + intl: PropTypes.object.isRequired, + dispatch: PropTypes.func.isRequired, + }; + + handleFollow = () => { + const { account, dispatch } = this.props; + + if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) { + dispatch(unfollowAccount(account.get('id'))); + } else { + dispatch(followAccount(account.get('id'))); + } + } + + render () { + const { account, intl } = this.props; + + let button; + + if (account.getIn(['relationship', 'following'])) { + button = ; + } else { + button = ; + } + + return ( +
+
+ +
+ + + +
{getFirstSentence(account.get('note_plain'))}
+
+ +
+ {button} +
+
+
+ ); + } + +} diff --git a/app/javascript/flavours/glitch/features/follow_recommendations/index.js b/app/javascript/flavours/glitch/features/follow_recommendations/index.js new file mode 100644 index 000000000..ac75062e0 --- /dev/null +++ b/app/javascript/flavours/glitch/features/follow_recommendations/index.js @@ -0,0 +1,95 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { connect } from 'react-redux'; +import { FormattedMessage } from 'react-intl'; +import { fetchSuggestions } from 'flavours/glitch/actions/suggestions'; +import { changeSetting, saveSettings } from 'flavours/glitch/actions/settings'; +import { requestBrowserPermission } from 'flavours/glitch/actions/notifications'; +import Column from 'flavours/glitch/features/ui/components/column'; +import Account from './components/account'; +import Logo from 'flavours/glitch/components/logo'; +import imageGreeting from 'mastodon/../images/elephant_ui_greeting.svg'; +import Button from 'flavours/glitch/components/button'; + +const mapStateToProps = state => ({ + suggestions: state.getIn(['suggestions', 'items']), + isLoading: state.getIn(['suggestions', 'isLoading']), +}); + +export default @connect(mapStateToProps) +class FollowRecommendations extends ImmutablePureComponent { + + static contextTypes = { + router: PropTypes.object.isRequired, + }; + + static propTypes = { + dispatch: PropTypes.func.isRequired, + suggestions: ImmutablePropTypes.list, + isLoading: PropTypes.bool, + }; + + componentDidMount () { + const { dispatch, suggestions } = this.props; + + // Don't re-fetch if we're e.g. navigating backwards to this page, + // since we don't want followed accounts to disappear from the list + + if (suggestions.size === 0) { + dispatch(fetchSuggestions(true)); + } + } + + handleDone = () => { + const { dispatch } = this.props; + const { router } = this.context; + + dispatch(requestBrowserPermission((permission) => { + if (permission === 'granted') { + dispatch(changeSetting(['notifications', 'alerts', 'follow'], true)); + dispatch(changeSetting(['notifications', 'alerts', 'favourite'], true)); + dispatch(changeSetting(['notifications', 'alerts', 'reblog'], true)); + dispatch(changeSetting(['notifications', 'alerts', 'mention'], true)); + dispatch(changeSetting(['notifications', 'alerts', 'poll'], true)); + dispatch(changeSetting(['notifications', 'alerts', 'status'], true)); + dispatch(saveSettings()); + } + })); + + router.history.push('/timelines/home'); + } + + render () { + const { suggestions, isLoading } = this.props; + + return ( + +
+
+ +

+

+
+ + {!isLoading && ( + +
+ {suggestions.map(suggestion => ( + + ))} +
+ +
+ + +
+
+ )} +
+
+ ); + } + +} diff --git a/app/javascript/flavours/glitch/features/ui/index.js b/app/javascript/flavours/glitch/features/ui/index.js index 61a34fd2b..1149eb14e 100644 --- a/app/javascript/flavours/glitch/features/ui/index.js +++ b/app/javascript/flavours/glitch/features/ui/index.js @@ -50,9 +50,11 @@ import { Search, GettingStartedMisc, Directory, + FollowRecommendations, } from 'flavours/glitch/util/async-components'; import { HotKeys } from 'react-hotkeys'; import { me } from 'flavours/glitch/util/initial_state'; +import { closeOnboarding, INTRODUCTION_VERSION } from 'flavours/glitch/actions/onboarding'; import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; // Dummy import, to make sure that ends up in the application bundle. @@ -75,6 +77,7 @@ const mapStateToProps = state => ({ showFaviconBadge: state.getIn(['local_settings', 'notifications', 'favicon_badge']), hicolorPrivacyIcons: state.getIn(['local_settings', 'hicolor_privacy_icons']), moved: state.getIn(['accounts', me, 'moved']) && state.getIn(['accounts', state.getIn(['accounts', me, 'moved'])]), + firstLaunch: state.getIn(['settings', 'introductionVersion'], 0) < INTRODUCTION_VERSION, }); const keyMap = { @@ -207,6 +210,7 @@ class SwitchingColumnsArea extends React.PureComponent { + @@ -260,6 +264,7 @@ class UI extends React.Component { unreadNotifications: PropTypes.number, showFaviconBadge: PropTypes.bool, moved: PropTypes.map, + firstLaunch: PropTypes.bool, }; state = { @@ -378,6 +383,12 @@ class UI extends React.Component { this.favicon = new Favico({ animation:"none" }); + // On first launch, redirect to the follow recommendations page + if (this.props.firstLaunch) { + this.context.router.history.replace('/start'); + this.props.dispatch(closeOnboarding()); + } + this.props.dispatch(fetchMarkers()); this.props.dispatch(expandHomeTimeline()); this.props.dispatch(expandNotifications()); diff --git a/app/javascript/flavours/glitch/styles/components/accounts.scss b/app/javascript/flavours/glitch/styles/components/accounts.scss index 6a629353d..377cdd91f 100644 --- a/app/javascript/flavours/glitch/styles/components/accounts.scss +++ b/app/javascript/flavours/glitch/styles/components/accounts.scss @@ -11,6 +11,19 @@ overflow: hidden; text-decoration: none; font-size: 14px; + + &--with-note { + strong { + display: inline; + } + } + } + + &__note { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + color: $ui-secondary-color; } &.small { @@ -26,6 +39,16 @@ } } +.follow-recommendations-account { + .icon-button { + color: $ui-primary-color; + + &.active { + color: $valid-value-color; + } + } +} + .account__wrapper { display: flex; } diff --git a/app/javascript/flavours/glitch/styles/components/columns.scss b/app/javascript/flavours/glitch/styles/components/columns.scss index 42d64c135..c6045e96e 100644 --- a/app/javascript/flavours/glitch/styles/components/columns.scss +++ b/app/javascript/flavours/glitch/styles/components/columns.scss @@ -796,3 +796,46 @@ text-align: center; } } + +.column-title { + text-align: center; + padding: 40px; + + .logo { + fill: $primary-text-color; + width: 50px; + margin: 0 auto; + margin-bottom: 40px; + } + + h3 { + font-size: 24px; + line-height: 1.5; + font-weight: 700; + margin-bottom: 10px; + } + + p { + font-size: 16px; + line-height: 24px; + font-weight: 400; + color: $darker-text-color; + } +} + +.column-actions { + display: flex; + align-items: center; + justify-content: center; + padding: 40px; + padding-top: 40px; + padding-bottom: 200px; + + &__background { + position: absolute; + left: 0; + bottom: 0; + height: 220px; + width: auto; + } +} diff --git a/app/javascript/flavours/glitch/util/async-components.js b/app/javascript/flavours/glitch/util/async-components.js index 26255bbb7..a6c6ab0ab 100644 --- a/app/javascript/flavours/glitch/util/async-components.js +++ b/app/javascript/flavours/glitch/util/async-components.js @@ -169,3 +169,7 @@ export function Tesseract () { export function Directory () { return import(/* webpackChunkName: "features/glitch/async/directory" */'flavours/glitch/features/directory'); } + +export function FollowRecommendations () { + return import(/* webpackChunkName: "features/glitch/async/follow_recommendations" */'flavours/glitch/features/follow_recommendations'); +} -- cgit From b571bc3278e39fda2caaa6663cdfd0015eb716b7 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 5 May 2021 23:57:29 +0200 Subject: [Glitch] Add empty state message for follow recommendations in web UI Port 8d75bd002da5011237b537b522a5b6248e99b9d5 to glitch-soc Signed-off-by: Claire --- .../glitch/features/follow_recommendations/index.js | 10 +++++++--- .../flavours/glitch/styles/components/columns.scss | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) (limited to 'app/javascript/flavours/glitch/styles/components') diff --git a/app/javascript/flavours/glitch/features/follow_recommendations/index.js b/app/javascript/flavours/glitch/features/follow_recommendations/index.js index ac75062e0..aea841bd6 100644 --- a/app/javascript/flavours/glitch/features/follow_recommendations/index.js +++ b/app/javascript/flavours/glitch/features/follow_recommendations/index.js @@ -75,10 +75,14 @@ class FollowRecommendations extends ImmutablePureComponent { {!isLoading && ( -
- {suggestions.map(suggestion => ( +
+ {suggestions.size > 0 ? suggestions.map(suggestion => ( - ))} + )) : ( +
+ +
+ )}
diff --git a/app/javascript/flavours/glitch/styles/components/columns.scss b/app/javascript/flavours/glitch/styles/components/columns.scss index c6045e96e..7f3d27dba 100644 --- a/app/javascript/flavours/glitch/styles/components/columns.scss +++ b/app/javascript/flavours/glitch/styles/components/columns.scss @@ -839,3 +839,19 @@ width: auto; } } + +.column-list { + margin: 0 20px; + border: 1px solid lighten($ui-base-color, 8%); + background: darken($ui-base-color, 2%); + border-radius: 4px; + + &__empty-message { + padding: 40px; + text-align: center; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color: $darker-text-color; + } +} -- cgit From 50b100df00dc03d554acc9f3ca6f5845edcadbd2 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 27 Nov 2020 03:24:11 +0100 Subject: Change media modals look in web UI Port 1e89e2ed988d2103ecd46e06476d863cb40c57c7 to glitch-soc Signed-off-by: Claire --- .../flavours/glitch/components/modal_root.js | 18 ++- .../flavours/glitch/components/status.js | 19 ++- .../flavours/glitch/containers/status_container.js | 8 +- .../glitch/features/account_gallery/index.js | 9 +- .../picture_in_picture/components/footer.js | 28 +++- .../flavours/glitch/features/status/index.js | 8 +- .../glitch/features/ui/components/media_modal.js | 174 +++++++++++++++++---- .../glitch/features/ui/components/modal_root.js | 13 +- .../glitch/features/ui/components/video_modal.js | 20 +-- .../flavours/glitch/features/video/index.js | 5 +- .../flavours/glitch/styles/components/boost.scss | 36 +++-- .../flavours/glitch/styles/components/index.scss | 4 +- .../flavours/glitch/styles/components/media.scss | 129 +++++++++------ 13 files changed, 330 insertions(+), 141 deletions(-) (limited to 'app/javascript/flavours/glitch/styles/components') diff --git a/app/javascript/flavours/glitch/components/modal_root.js b/app/javascript/flavours/glitch/components/modal_root.js index 13a8e8702..024adf5a1 100644 --- a/app/javascript/flavours/glitch/components/modal_root.js +++ b/app/javascript/flavours/glitch/components/modal_root.js @@ -2,6 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import 'wicg-inert'; import { createBrowserHistory } from 'history'; +import { normal } from 'color-blend'; export default class ModalRoot extends React.PureComponent { static contextTypes = { @@ -11,6 +12,11 @@ export default class ModalRoot extends React.PureComponent { static propTypes = { children: PropTypes.node, onClose: PropTypes.func.isRequired, + backgroundColor: PropTypes.shape({ + r: PropTypes.number, + g: PropTypes.number, + b: PropTypes.number, + }), noEsc: PropTypes.bool, }; @@ -68,9 +74,7 @@ export default class ModalRoot extends React.PureComponent { Promise.resolve().then(() => { this.activeElement.focus({ preventScroll: true }); this.activeElement = null; - }).catch((error) => { - console.error(error); - }); + }).catch(console.error); this.handleModalClose(); } @@ -120,10 +124,16 @@ export default class ModalRoot extends React.PureComponent { ); } + let backgroundColor = null; + + if (this.props.backgroundColor) { + backgroundColor = normal({ ...this.props.backgroundColor, a: 1 }, { r: 0, g: 0, b: 0, a: 0.3 }); + } + return (
-
+
{children}
diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js index fcbf4be8c..0f8c94b11 100644 --- a/app/javascript/flavours/glitch/components/status.js +++ b/app/javascript/flavours/glitch/components/status.js @@ -379,21 +379,24 @@ class Status extends ImmutablePureComponent { }; handleOpenVideo = (media, options) => { - this.props.onOpenVideo(media, options); + this.props.onOpenVideo(this.props.status.get('id'), media, options); + } + + handleOpenMedia = (media, index) => { + this.props.onOpenMedia(this.props.status.get('id'), media, index); } handleHotkeyOpenMedia = e => { const { status, onOpenMedia, onOpenVideo } = this.props; + const statusId = status.get('id'); e.preventDefault(); if (status.get('media_attachments').size > 0) { - if (status.getIn(['media_attachments', 0, 'type']) === 'audio') { - // TODO: toggle play/paused? - } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') { - onOpenVideo(status.getIn(['media_attachments', 0]), { startTime: 0 }); + if (status.getIn(['media_attachments', 0, 'type']) === 'video') { + onOpenVideo(statusId, status.getIn(['media_attachments', 0]), { startTime: 0 }); } else { - onOpenMedia(status.get('media_attachments'), 0); + onOpenMedia(statusId, status.get('media_attachments'), 0); } } } @@ -657,7 +660,7 @@ class Status extends ImmutablePureComponent { letterbox={settings.getIn(['media', 'letterbox'])} fullwidth={settings.getIn(['media', 'fullwidth'])} hidden={isCollapsed || !isExpanded} - onOpenMedia={this.props.onOpenMedia} + onOpenMedia={this.handleOpenMedia} cacheWidth={this.props.cacheMediaWidth} defaultWidth={this.props.cachedMediaWidth} visible={this.state.showMedia} @@ -675,7 +678,7 @@ class Status extends ImmutablePureComponent { } else if (status.get('card') && settings.get('inline_preview_cards')) { media = ( ({ dispatch(mentionCompose(account, router)); }, - onOpenMedia (media, index) { - dispatch(openModal('MEDIA', { media, index })); + onOpenMedia (statusId, media, index) { + dispatch(openModal('MEDIA', { statusId, media, index })); }, - onOpenVideo (media, options) { - dispatch(openModal('VIDEO', { media, options })); + onOpenVideo (statusId, media, options) { + dispatch(openModal('VIDEO', { statusId, media, options })); }, onBlock (status) { diff --git a/app/javascript/flavours/glitch/features/account_gallery/index.js b/app/javascript/flavours/glitch/features/account_gallery/index.js index 81203e3f8..2a43d1ed2 100644 --- a/app/javascript/flavours/glitch/features/account_gallery/index.js +++ b/app/javascript/flavours/glitch/features/account_gallery/index.js @@ -114,15 +114,18 @@ class AccountGallery extends ImmutablePureComponent { } handleOpenMedia = attachment => { + const { dispatch } = this.props; + const statusId = attachment.getIn(['status', 'id']); + if (attachment.get('type') === 'video') { - this.props.dispatch(openModal('VIDEO', { media: attachment, status: attachment.get('status'), options: { autoPlay: true } })); + dispatch(openModal('VIDEO', { media: attachment, statusId, options: { autoPlay: true } })); } else if (attachment.get('type') === 'audio') { - this.props.dispatch(openModal('AUDIO', { media: attachment, status: attachment.get('status'), options: { autoPlay: true } })); + dispatch(openModal('AUDIO', { media: attachment, statusId, options: { autoPlay: true } })); } else { const media = attachment.getIn(['status', 'media_attachments']); const index = media.findIndex(x => x.get('id') === attachment.get('id')); - this.props.dispatch(openModal('MEDIA', { media, index, status: attachment.get('status') })); + dispatch(openModal('MEDIA', { media, index, statusId })); } } diff --git a/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js b/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js index d8989ec61..fcb2df527 100644 --- a/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js +++ b/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js @@ -23,6 +23,7 @@ const messages = defineMessages({ favourite: { id: 'status.favourite', defaultMessage: 'Favourite' }, replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' }, replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' }, + open: { id: 'status.open', defaultMessage: 'Expand this status' }, }); const makeMapStateToProps = () => { @@ -52,11 +53,19 @@ class Footer extends ImmutablePureComponent { dispatch: PropTypes.func.isRequired, askReplyConfirmation: PropTypes.bool, showReplyCount: PropTypes.bool, + withOpenButton: PropTypes.bool, + onClose: PropTypes.func, }; _performReply = () => { - const { dispatch, status } = this.props; - dispatch(replyCompose(status, this.context.router.history)); + const { dispatch, status, onClose } = this.props; + const { router } = this.context; + + if (onClose) { + onClose(); + } + + dispatch(replyCompose(status, router.history)); }; handleReplyClick = () => { @@ -100,8 +109,20 @@ class Footer extends ImmutablePureComponent { } }; + handleOpenClick = e => { + const { router } = this.context; + + if (e.button !== 0 || !router) { + return; + } + + const { status } = this.props; + + router.history.push(`/statuses/${status.get('id')}`); + } + render () { - const { status, intl, showReplyCount } = this.props; + const { status, intl, showReplyCount, withOpenButton } = this.props; const publicStatus = ['public', 'unlisted'].includes(status.get('visibility')); const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility') === 'private'; @@ -156,6 +177,7 @@ class Footer extends ImmutablePureComponent { {replyButton} + {withOpenButton && }
); } diff --git a/app/javascript/flavours/glitch/features/status/index.js b/app/javascript/flavours/glitch/features/status/index.js index 21e441407..513a6227f 100644 --- a/app/javascript/flavours/glitch/features/status/index.js +++ b/app/javascript/flavours/glitch/features/status/index.js @@ -316,11 +316,11 @@ class Status extends ImmutablePureComponent { } handleOpenMedia = (media, index) => { - this.props.dispatch(openModal('MEDIA', { media, index })); + this.props.dispatch(openModal('MEDIA', { statusId: this.props.status.get('id'), media, index })); } handleOpenVideo = (media, options) => { - this.props.dispatch(openModal('VIDEO', { media, options })); + this.props.dispatch(openModal('VIDEO', { statusId: this.props.status.get('id'), media, options })); } handleHotkeyOpenMedia = e => { @@ -329,9 +329,7 @@ class Status extends ImmutablePureComponent { e.preventDefault(); if (status.get('media_attachments').size > 0) { - if (status.getIn(['media_attachments', 0, 'type']) === 'audio') { - // TODO: toggle play/paused? - } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') { + if (status.getIn(['media_attachments', 0, 'type']) === 'video') { this.handleOpenVideo(status.getIn(['media_attachments', 0]), { startTime: 0 }); } else { this.handleOpenMedia(status.get('media_attachments'), 0); diff --git a/app/javascript/flavours/glitch/features/ui/components/media_modal.js b/app/javascript/flavours/glitch/features/ui/components/media_modal.js index e37df7208..7b07c557f 100644 --- a/app/javascript/flavours/glitch/features/ui/components/media_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/media_modal.js @@ -4,12 +4,13 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import Video from 'flavours/glitch/features/video'; import classNames from 'classnames'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import IconButton from 'flavours/glitch/components/icon_button'; import ImmutablePureComponent from 'react-immutable-pure-component'; import ImageLoader from './image_loader'; import Icon from 'flavours/glitch/components/icon'; import GIFV from 'flavours/glitch/components/gifv'; +import Footer from 'flavours/glitch/features/picture_in_picture/components/footer'; const messages = defineMessages({ close: { id: 'lightbox.close', defaultMessage: 'Close' }, @@ -17,6 +18,111 @@ const messages = defineMessages({ next: { id: 'lightbox.next', defaultMessage: 'Next' }, }); +const digitCharacters = [ + '0', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + 'A', + 'B', + 'C', + 'D', + 'E', + 'F', + 'G', + 'H', + 'I', + 'J', + 'K', + 'L', + 'M', + 'N', + 'O', + 'P', + 'Q', + 'R', + 'S', + 'T', + 'U', + 'V', + 'W', + 'X', + 'Y', + 'Z', + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i', + 'j', + 'k', + 'l', + 'm', + 'n', + 'o', + 'p', + 'q', + 'r', + 's', + 't', + 'u', + 'v', + 'w', + 'x', + 'y', + 'z', + '#', + '$', + '%', + '*', + '+', + ',', + '-', + '.', + ':', + ';', + '=', + '?', + '@', + '[', + ']', + '^', + '_', + '{', + '|', + '}', + '~', +]; + +const decode83 = (str) => { + let value = 0; + let c, digit; + + for (let i = 0; i < str.length; i++) { + c = str[i]; + digit = digitCharacters.indexOf(c); + value = value * 83 + digit; + } + + return value; +}; + +const decodeRGB = int => ({ + r: Math.max(0, (int >> 16)), + g: Math.max(0, (int >> 8) & 255), + b: Math.max(0, (int & 255)), +}); + export default @injectIntl class MediaModal extends ImmutablePureComponent { @@ -26,10 +132,11 @@ class MediaModal extends ImmutablePureComponent { static propTypes = { media: ImmutablePropTypes.list.isRequired, - status: ImmutablePropTypes.map, + statusId: PropTypes.string, index: PropTypes.number.isRequired, onClose: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, + onChangeBackgroundColor: PropTypes.func.isRequired, }; state = { @@ -64,6 +171,7 @@ class MediaModal extends ImmutablePureComponent { handleChangeIndex = (e) => { const index = Number(e.currentTarget.getAttribute('data-index')); + this.setState({ index: index % this.props.media.size, zoomButtonHidden: true, @@ -87,10 +195,12 @@ class MediaModal extends ImmutablePureComponent { componentDidMount () { window.addEventListener('keydown', this.handleKeyDown, false); + this._sendBackgroundColor(); } componentWillUnmount () { window.removeEventListener('keydown', this.handleKeyDown); + this.props.onChangeBackgroundColor(null); } getIndex () { @@ -106,30 +216,35 @@ class MediaModal extends ImmutablePureComponent { handleStatusClick = e => { if (e.button === 0 && !(e.ctrlKey || e.metaKey)) { e.preventDefault(); - this.context.router.history.push(`/statuses/${this.props.status.get('id')}`); + this.context.router.history.push(`/statuses/${this.props.statusId}`); } + + this._sendBackgroundColor(); + } + + componentDidUpdate (prevProps, prevState) { + if (prevState.index !== this.state.index) { + this._sendBackgroundColor(); + } + } + + _sendBackgroundColor () { + const { media, onChangeBackgroundColor } = this.props; + const index = this.getIndex(); + const backgroundColor = decodeRGB(decode83(media.getIn([index, 'blurhash']).slice(2, 6))); + + onChangeBackgroundColor(backgroundColor); } render () { - const { media, status, intl, onClose } = this.props; + const { media, statusId, intl, onClose } = this.props; const { navigationHidden } = this.state; const index = this.getIndex(); - let pagination = []; const leftNav = media.size > 1 && ; const rightNav = media.size > 1 && ; - if (media.size > 1) { - pagination = media.map((item, i) => { - const classes = ['media-modal__button']; - if (i === index) { - classes.push('media-modal__button--active'); - } - return (
  • ); - }); - } - const content = media.map((image) => { const width = image.getIn(['meta', 'original', 'width']) || null; const height = image.getIn(['meta', 'original', 'height']) || null; @@ -197,13 +312,19 @@ class MediaModal extends ImmutablePureComponent { 'media-modal__navigation--hidden': navigationHidden, }); + let pagination; + + if (media.size > 1) { + pagination = media.map((item, i) => ( + + )); + } + return (
    -
    +
    1 })}> - -
    - )} - -
      - {pagination} -
    +
    + {pagination &&
      {pagination}
    } + {statusId &&
    } +
    ); diff --git a/app/javascript/flavours/glitch/features/ui/components/modal_root.js b/app/javascript/flavours/glitch/features/ui/components/modal_root.js index 488daf0cc..0fd70de34 100644 --- a/app/javascript/flavours/glitch/features/ui/components/modal_root.js +++ b/app/javascript/flavours/glitch/features/ui/components/modal_root.js @@ -55,6 +55,10 @@ export default class ModalRoot extends React.PureComponent { onClose: PropTypes.func.isRequired, }; + state = { + backgroundColor: null, + }; + getSnapshotBeforeUpdate () { return { visible: !!this.props.type }; } @@ -69,6 +73,10 @@ export default class ModalRoot extends React.PureComponent { } } + setBackgroundColor = color => { + this.setState({ backgroundColor: color }); + } + renderLoading = modalId => () => { return ['MEDIA', 'VIDEO', 'BOOST', 'FAVOURITE', 'DOODLE', 'CONFIRM', 'ACTIONS'].indexOf(modalId) === -1 ? : null; } @@ -81,13 +89,14 @@ export default class ModalRoot extends React.PureComponent { render () { const { type, props, onClose } = this.props; + const { backgroundColor } = this.state; const visible = !!type; return ( - + {visible && ( - {(SpecificComponent) => } + {(SpecificComponent) => } )} diff --git a/app/javascript/flavours/glitch/features/ui/components/video_modal.js b/app/javascript/flavours/glitch/features/ui/components/video_modal.js index b0a4f3f03..62273f252 100644 --- a/app/javascript/flavours/glitch/features/ui/components/video_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/video_modal.js @@ -3,9 +3,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import Video from 'flavours/glitch/features/video'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { FormattedMessage } from 'react-intl'; -import classNames from 'classnames'; -import Icon from 'flavours/glitch/components/icon'; export default class VideoModal extends ImmutablePureComponent { @@ -15,7 +12,7 @@ export default class VideoModal extends ImmutablePureComponent { static propTypes = { media: ImmutablePropTypes.map.isRequired, - status: ImmutablePropTypes.map, + statusId: PropTypes.string, options: PropTypes.shape({ startTime: PropTypes.number, autoPlay: PropTypes.bool, @@ -24,15 +21,8 @@ export default class VideoModal extends ImmutablePureComponent { onClose: PropTypes.func.isRequired, }; - handleStatusClick = e => { - if (e.button === 0 && !(e.ctrlKey || e.metaKey)) { - e.preventDefault(); - this.context.router.history.push(`/statuses/${this.props.status.get('id')}`); - } - } - render () { - const { media, status, onClose } = this.props; + const { media, onClose } = this.props; const options = this.props.options || {}; return ( @@ -51,12 +41,6 @@ export default class VideoModal extends ImmutablePureComponent { alt={media.get('description')} />
    - - {status && ( -
    - -
    - )}
    ); } diff --git a/app/javascript/flavours/glitch/features/video/index.js b/app/javascript/flavours/glitch/features/video/index.js index a81311c67..0d267c3a1 100644 --- a/app/javascript/flavours/glitch/features/video/index.js +++ b/app/javascript/flavours/glitch/features/video/index.js @@ -120,7 +120,6 @@ class Video extends React.PureComponent { deployPictureInPicture: PropTypes.func, preventPlayback: PropTypes.bool, blurhash: PropTypes.string, - link: PropTypes.node, autoPlay: PropTypes.bool, volume: PropTypes.number, muted: PropTypes.bool, @@ -548,7 +547,7 @@ class Video extends React.PureComponent { } render () { - const { preview, src, inline, onOpenVideo, onCloseVideo, intl, alt, letterbox, fullwidth, detailed, sensitive, link, editable, blurhash } = this.props; + const { preview, src, inline, onOpenVideo, onCloseVideo, intl, alt, letterbox, fullwidth, detailed, sensitive, editable, blurhash } = this.props; const { containerWidth, currentTime, duration, volume, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state; const progress = Math.min((currentTime / duration) * 100, 100); const playerStyle = {}; @@ -666,8 +665,6 @@ class Video extends React.PureComponent { {formatTime(Math.floor(duration))} )} - - {link && {link}}
    diff --git a/app/javascript/flavours/glitch/styles/components/boost.scss b/app/javascript/flavours/glitch/styles/components/boost.scss index c8b97fabe..fb1451cb2 100644 --- a/app/javascript/flavours/glitch/styles/components/boost.scss +++ b/app/javascript/flavours/glitch/styles/components/boost.scss @@ -1,22 +1,32 @@ -button.icon-button i.fa-retweet { - background-image: url("data:image/svg+xml;utf8,"); +button.icon-button { + i.fa-retweet { + background-image: url("data:image/svg+xml;utf8,"); + } - &:hover { + &:hover i.fa-retweet { background-image: url("data:image/svg+xml;utf8,"); } -} -button.icon-button.reblogPrivate i.fa-retweet { - background-image: url("data:image/svg+xml;utf8,"); + &.reblogPrivate { + i.fa-retweet { + background-image: url("data:image/svg+xml;utf8,"); + } - &:hover { - background-image: url("data:image/svg+xml;utf8,"); + &:hover i.fa-retweet { + background-image: url("data:image/svg+xml;utf8,"); + } + } + + &.disabled { + i.fa-retweet, + &:hover i.fa-retweet { + background-image: url("data:image/svg+xml;utf8,"); + } } -} -// Disabled variant -button.icon-button.disabled i.fa-retweet { - &, &:hover { - background-image: url("data:image/svg+xml;utf8,"); + .media-modal__overlay .picture-in-picture__footer & { + i.fa-retweet { + background-image: url("data:image/svg+xml;utf8,"); + } } } diff --git a/app/javascript/flavours/glitch/styles/components/index.scss b/app/javascript/flavours/glitch/styles/components/index.scss index aada9a928..16045d5ee 100644 --- a/app/javascript/flavours/glitch/styles/components/index.scss +++ b/app/javascript/flavours/glitch/styles/components/index.scss @@ -334,11 +334,11 @@ } } -.star-icon.active { +.icon-button.star-icon.active { color: $gold-star; } -.bookmark-icon.active { +.icon-button.bookmark-icon.active { color: $red-bookmark; } diff --git a/app/javascript/flavours/glitch/styles/components/media.scss b/app/javascript/flavours/glitch/styles/components/media.scss index 6cbcb05bd..88212457f 100644 --- a/app/javascript/flavours/glitch/styles/components/media.scss +++ b/app/javascript/flavours/glitch/styles/components/media.scss @@ -187,16 +187,19 @@ height: 100%; position: relative; - .extended-video-player { - width: 100%; - height: 100%; - display: flex; - align-items: center; - justify-content: center; + &__close, + &__zoom-button { + color: rgba($white, 0.7); - video { - max-width: $media-modal-media-max-width; - max-height: $media-modal-media-max-height; + &:hover, + &:focus, + &:active { + color: $white; + background-color: rgba($white, 0.15); + } + + &:focus { + background-color: rgba($white, 0.3); } } } @@ -233,10 +236,10 @@ } .media-modal__nav { - background: rgba($base-overlay-background, 0.5); + background: transparent; box-sizing: border-box; border: 0; - color: $primary-text-color; + color: rgba($primary-text-color, 0.7); cursor: pointer; display: flex; align-items: center; @@ -247,6 +250,12 @@ position: absolute; top: 0; bottom: 0; + + &:hover, + &:focus, + &:active { + color: $primary-text-color; + } } .media-modal__nav--left { @@ -257,58 +266,86 @@ right: 0; } -.media-modal__pagination { - width: 100%; - text-align: center; +.media-modal__overlay { + max-width: 600px; position: absolute; left: 0; - bottom: 20px; - pointer-events: none; -} + right: 0; + bottom: 0; + margin: 0 auto; -.media-modal__meta { - text-align: center; - position: absolute; - left: 0; - bottom: 20px; - width: 100%; - pointer-events: none; + .picture-in-picture__footer { + border-radius: 0; + background: transparent; + padding: 20px 0; - &--shifted { - bottom: 62px; - } + .icon-button { + color: $white; - a { - pointer-events: auto; - text-decoration: none; - font-weight: 500; - color: $ui-secondary-color; + &:hover, + &:focus, + &:active { + color: $white; + background-color: rgba($white, 0.15); + } - &:hover, - &:focus, - &:active { - text-decoration: underline; + &:focus { + background-color: rgba($white, 0.3); + } + + &.active { + color: $highlight-text-color; + + &:hover, + &:focus, + &:active { + background: rgba($highlight-text-color, 0.15); + } + + &:focus { + background: rgba($highlight-text-color, 0.3); + } + } + + &.star-icon.active { + color: $gold-star; + + &:hover, + &:focus, + &:active { + background: rgba($gold-star, 0.15); + } + + &:focus { + background: rgba($gold-star, 0.3); + } + } } } } -.media-modal__page-dot { - display: inline-block; +.media-modal__pagination { + display: flex; + justify-content: center; + margin-bottom: 20px; } -.media-modal__button { +.media-modal__page-dot { + flex: 0 0 auto; background-color: $white; - height: 12px; - width: 12px; - border-radius: 6px; - margin: 10px; + opacity: 0.4; + height: 6px; + width: 6px; + border-radius: 50%; + margin: 0 4px; padding: 0; border: 0; font-size: 0; -} + transition: opacity .2s ease-in-out; -.media-modal__button--active { - background-color: $ui-highlight-color; + &.active { + opacity: 1; + } } .media-modal__close { -- cgit From d8e756eda8b8e838c8bb6bed170aab37fde837b7 Mon Sep 17 00:00:00 2001 From: ThibG Date: Wed, 23 Dec 2020 19:55:23 +0100 Subject: [Glitch] Fix media modal buttons not showing up on mobile Port b08d2d4f78e143189c0dc3470a8dc186bf441419 to glitch-soc Co-authored-by: Claire Signed-off-by: Claire --- .../flavours/glitch/styles/components/status.scss | 15 --------------- 1 file changed, 15 deletions(-) (limited to 'app/javascript/flavours/glitch/styles/components') diff --git a/app/javascript/flavours/glitch/styles/components/status.scss b/app/javascript/flavours/glitch/styles/components/status.scss index 4248657ad..e906a7261 100644 --- a/app/javascript/flavours/glitch/styles/components/status.scss +++ b/app/javascript/flavours/glitch/styles/components/status.scss @@ -1122,21 +1122,6 @@ a.status-card.compact:hover { .audio-player { border-radius: 0; } - - @media screen and (max-width: 415px) { - width: 210px; - bottom: 10px; - right: 10px; - - &__footer { - display: none; - } - - .video-player, - .audio-player { - border-radius: 0 0 4px 4px; - } - } } .picture-in-picture-placeholder { -- cgit From 196cef8977a8dacd56ee910cd9d482faae23047c Mon Sep 17 00:00:00 2001 From: Marcin Mikołajczak Date: Wed, 24 Mar 2021 13:51:32 +0100 Subject: [Glitch] Add transition to media modal background MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Port f8e50eaea351d743bf80e65c2178c4ed46d65881 to glitch-soc Signed-off-by: marcin mikołajczak Signed-off-by: Claire --- app/javascript/flavours/glitch/styles/components/modal.scss | 1 + 1 file changed, 1 insertion(+) (limited to 'app/javascript/flavours/glitch/styles/components') diff --git a/app/javascript/flavours/glitch/styles/components/modal.scss b/app/javascript/flavours/glitch/styles/components/modal.scss index f5acd814e..671323bc5 100644 --- a/app/javascript/flavours/glitch/styles/components/modal.scss +++ b/app/javascript/flavours/glitch/styles/components/modal.scss @@ -14,6 +14,7 @@ right: 0; bottom: 0; background: rgba($base-overlay-background, 0.7); + transition: background 0.5s; } .modal-root__container { -- cgit