From eed2c9dd44d7f58bbb54554c2271c465b67b827c Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Fri, 19 Apr 2019 20:14:32 +0200 Subject: Rename flavours/glitch/features/drawer to flavours/glitch/features/compose --- .../glitch/features/compose/account/index.js | 76 ++++++++++ .../glitch/features/compose/header/index.js | 127 ++++++++++++++++ .../flavours/glitch/features/compose/index.js | 166 +++++++++++++++++++++ .../glitch/features/compose/results/index.js | 117 +++++++++++++++ .../glitch/features/compose/search/index.js | 152 +++++++++++++++++++ .../glitch/features/compose/search/popout/index.js | 109 ++++++++++++++ 6 files changed, 747 insertions(+) create mode 100644 app/javascript/flavours/glitch/features/compose/account/index.js create mode 100644 app/javascript/flavours/glitch/features/compose/header/index.js create mode 100644 app/javascript/flavours/glitch/features/compose/index.js create mode 100644 app/javascript/flavours/glitch/features/compose/results/index.js create mode 100644 app/javascript/flavours/glitch/features/compose/search/index.js create mode 100644 app/javascript/flavours/glitch/features/compose/search/popout/index.js (limited to 'app/javascript/flavours/glitch/features/compose') diff --git a/app/javascript/flavours/glitch/features/compose/account/index.js b/app/javascript/flavours/glitch/features/compose/account/index.js new file mode 100644 index 000000000..552848641 --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/account/index.js @@ -0,0 +1,76 @@ +// Package imports. +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { + FormattedMessage, + defineMessages, +} from 'react-intl'; + +// Components. +import Avatar from 'flavours/glitch/components/avatar'; +import Permalink from 'flavours/glitch/components/permalink'; + +// Utils. +import { hiddenComponent } from 'flavours/glitch/util/react_helpers'; +import { profileLink } from 'flavours/glitch/util/backend_links'; + +// Messages. +const messages = defineMessages({ + edit: { + defaultMessage: 'Edit profile', + id: 'navigation_bar.edit_profile', + }, +}); + +// The component. +export default function DrawerAccount ({ account }) { + + // We need an account to render. + if (!account) { + return ( +
+ { profileLink !== undefined && ( + + + + )} +
+ ); + } + + // The result. + return ( +
+ + {account.get('acct')} + + + + @{account.get('acct')} + + { profileLink !== undefined && ( + + )} +
+ ); +} + +// Props. +DrawerAccount.propTypes = { account: ImmutablePropTypes.map }; diff --git a/app/javascript/flavours/glitch/features/compose/header/index.js b/app/javascript/flavours/glitch/features/compose/header/index.js new file mode 100644 index 000000000..da5599732 --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/header/index.js @@ -0,0 +1,127 @@ +// Package imports. +import PropTypes from 'prop-types'; +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { defineMessages } from 'react-intl'; +import { Link } from 'react-router-dom'; + +// Components. +import Icon from 'flavours/glitch/components/icon'; + +// Utils. +import { conditionalRender } from 'flavours/glitch/util/react_helpers'; +import { signOutLink } from 'flavours/glitch/util/backend_links'; + +// Messages. +const messages = defineMessages({ + community: { + defaultMessage: 'Local timeline', + id: 'navigation_bar.community_timeline', + }, + home_timeline: { + defaultMessage: 'Home', + id: 'tabs_bar.home', + }, + logout: { + defaultMessage: 'Logout', + id: 'navigation_bar.logout', + }, + notifications: { + defaultMessage: 'Notifications', + id: 'tabs_bar.notifications', + }, + public: { + defaultMessage: 'Federated timeline', + id: 'navigation_bar.public_timeline', + }, + settings: { + defaultMessage: 'App settings', + id: 'navigation_bar.app_settings', + }, + start: { + defaultMessage: 'Getting started', + id: 'getting_started.heading', + }, +}); + +// The component. +export default function DrawerHeader ({ + columns, + unreadNotifications, + showNotificationsBadge, + intl, + onSettingsClick, +}) { + + // Only renders the component if the column isn't being shown. + const renderForColumn = conditionalRender.bind(null, + columnId => !columns || !columns.some( + column => column.get('id') === columnId + ) + ); + + // The result. + return ( + + ); +} + +// Props. +DrawerHeader.propTypes = { + columns: ImmutablePropTypes.list, + unreadNotifications: PropTypes.number, + showNotificationsBadge: PropTypes.bool, + intl: PropTypes.object, + onSettingsClick: PropTypes.func, +}; diff --git a/app/javascript/flavours/glitch/features/compose/index.js b/app/javascript/flavours/glitch/features/compose/index.js new file mode 100644 index 000000000..cb261f9d6 --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/index.js @@ -0,0 +1,166 @@ +// Package imports. +import React from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { injectIntl, defineMessages } from 'react-intl'; +import classNames from 'classnames'; + +// Actions. +import { openModal } from 'flavours/glitch/actions/modal'; +import { + changeSearch, + clearSearch, + showSearch, + submitSearch, +} from 'flavours/glitch/actions/search'; +import { cycleElefriendCompose } from 'flavours/glitch/actions/compose'; + +// Components. +import Composer from 'flavours/glitch/features/composer'; +import DrawerAccount from './account'; +import DrawerHeader from './header'; +import DrawerResults from './results'; +import DrawerSearch from './search'; + +// Utils. +import { me, mascot } from 'flavours/glitch/util/initial_state'; +import { wrap } from 'flavours/glitch/util/redux_helpers'; + +// Messages. +const messages = defineMessages({ + compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new toot' }, +}); + +// State mapping. +const mapStateToProps = state => ({ + account: state.getIn(['accounts', me]), + columns: state.getIn(['settings', 'columns']), + elefriend: state.getIn(['compose', 'elefriend']), + results: state.getIn(['search', 'results']), + searchHidden: state.getIn(['search', 'hidden']), + searchValue: state.getIn(['search', 'value']), + submitted: state.getIn(['search', 'submitted']), + unreadNotifications: state.getIn(['notifications', 'unread']), + showNotificationsBadge: state.getIn(['local_settings', 'notifications', 'tab_badge']), +}); + +// Dispatch mapping. +const mapDispatchToProps = (dispatch, { intl }) => ({ + onChange (value) { + dispatch(changeSearch(value)); + }, + onClear () { + dispatch(clearSearch()); + }, + onClickElefriend () { + dispatch(cycleElefriendCompose()); + }, + onShow () { + dispatch(showSearch()); + }, + onSubmit () { + dispatch(submitSearch()); + }, + onOpenSettings (e) { + e.preventDefault(); + e.stopPropagation(); + dispatch(openModal('SETTINGS', {})); + }, +}); + +// The component. +export default @connect(mapStateToProps, mapDispatchToProps) +@injectIntl +class Compose extends React.PureComponent { + static propTypes = { + intl: PropTypes.object.isRequired, + isSearchPage: PropTypes.bool, + multiColumn: PropTypes.bool, + + // State props. + account: ImmutablePropTypes.map, + columns: ImmutablePropTypes.list, + results: ImmutablePropTypes.map, + elefriend: PropTypes.number, + searchHidden: PropTypes.bool, + searchValue: PropTypes.string, + submitted: PropTypes.bool, + unreadNotifications: PropTypes.number, + showNotificationsBadge: PropTypes.bool, + + // Dispatch props. + onChange: PropTypes.func, + onClear: PropTypes.func, + onClickElefriend: PropTypes.func, + onShow: PropTypes.func, + onSubmit: PropTypes.func, + onOpenSettings: PropTypes.func, + }; + + // Rendering. + render () { + const { + account, + columns, + elefriend, + intl, + multiColumn, + onChange, + onClear, + onClickElefriend, + onOpenSettings, + onShow, + onSubmit, + results, + searchHidden, + searchValue, + submitted, + isSearchPage, + unreadNotifications, + showNotificationsBadge, + } = this.props; + const computedClass = classNames('drawer', `mbstobon-${elefriend}`); + + // The result. + return ( +
+ {multiColumn && ( + + )} + {(multiColumn || isSearchPage) && } +
+ {!isSearchPage &&
+ + + {multiColumn && ( +
+ {mascot ? :
+ )} +
} + + {(multiColumn || isSearchPage) && + } +
+
+ ); + } +} diff --git a/app/javascript/flavours/glitch/features/compose/results/index.js b/app/javascript/flavours/glitch/features/compose/results/index.js new file mode 100644 index 000000000..4574c0e1e --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/results/index.js @@ -0,0 +1,117 @@ +// Package imports. +import PropTypes from 'prop-types'; +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { + FormattedMessage, + defineMessages, +} from 'react-intl'; +import spring from 'react-motion/lib/spring'; +import { Link } from 'react-router-dom'; + +// Components. +import Icon from 'flavours/glitch/components/icon'; +import AccountContainer from 'flavours/glitch/containers/account_container'; +import StatusContainer from 'flavours/glitch/containers/status_container'; +import Hashtag from 'flavours/glitch/components/hashtag'; + +// Utils. +import Motion from 'flavours/glitch/util/optional_motion'; + +// Messages. +const messages = defineMessages({ + total: { + defaultMessage: '{count, number} {count, plural, one {result} other {results}}', + id: 'search_results.total', + }, +}); + +// The component. +export default function DrawerResults ({ + results, + visible, +}) { + const accounts = results ? results.get('accounts') : null; + const statuses = results ? results.get('statuses') : null; + const hashtags = results ? results.get('hashtags') : null; + + // This gets the total number of items. + const count = [accounts, statuses, hashtags].reduce(function (size, item) { + if (item && item.size) { + return size + item.size; + } + return size; + }, 0); + + // The result. + return ( + + {({ x }) => ( +
+
+ + +
+ {accounts && accounts.size ? ( +
+
+ + {accounts.map( + accountId => ( + + ) + )} +
+ ) : null} + {statuses && statuses.size ? ( +
+
+ + {statuses.map( + statusId => ( + + ) + )} +
+ ) : null} + {hashtags && hashtags.size ? ( +
+
+ + {hashtags.map(hashtag => )} +
+ ) : null} +
+ )} +
+ ); +} + +// Props. +DrawerResults.propTypes = { + results: ImmutablePropTypes.map, + visible: PropTypes.bool, +}; diff --git a/app/javascript/flavours/glitch/features/compose/search/index.js b/app/javascript/flavours/glitch/features/compose/search/index.js new file mode 100644 index 000000000..8cbb0906c --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/search/index.js @@ -0,0 +1,152 @@ +// Package imports. +import classNames from 'classnames'; +import PropTypes from 'prop-types'; +import React from 'react'; +import { + FormattedMessage, + defineMessages, +} from 'react-intl'; +import Overlay from 'react-overlays/lib/Overlay'; + +// Components. +import Icon from 'flavours/glitch/components/icon'; +import DrawerSearchPopout from './popout'; + +// Utils. +import { focusRoot } from 'flavours/glitch/util/dom_helpers'; +import { + assignHandlers, + hiddenComponent, +} from 'flavours/glitch/util/react_helpers'; + +// Messages. +const messages = defineMessages({ + placeholder: { + defaultMessage: 'Search', + id: 'search.placeholder', + }, +}); + +// Handlers. +const handlers = { + + handleBlur () { + this.setState({ expanded: false }); + }, + + handleChange ({ target: { value } }) { + const { onChange } = this.props; + if (onChange) { + onChange(value); + } + }, + + handleClear (e) { + const { + onClear, + submitted, + value, + } = this.props; + e.preventDefault(); // Prevents focus change ?? + if (onClear && (submitted || value && value.length)) { + onClear(); + } + }, + + handleFocus () { + const { onShow } = this.props; + this.setState({ expanded: true }); + if (onShow) { + onShow(); + } + }, + + handleKeyUp (e) { + const { onSubmit } = this.props; + switch (e.key) { + case 'Enter': + if (onSubmit) { + onSubmit(); + } + break; + case 'Escape': + focusRoot(); + } + }, +}; + +// The component. +export default class DrawerSearch extends React.PureComponent { + + // Constructor. + constructor (props) { + super(props); + assignHandlers(this, handlers); + this.state = { expanded: false }; + } + + // Rendering. + render () { + const { + handleBlur, + handleChange, + handleClear, + handleFocus, + handleKeyUp, + } = this.handlers; + const { + intl, + submitted, + value, + } = this.props; + const { expanded } = this.state; + const active = value && value.length || submitted; + const computedClass = classNames('drawer--search', { active }); + + return ( +
+ +
+ + +
+ +
+ ); + } + +} + +// Props. +DrawerSearch.propTypes = { + value: PropTypes.string, + submitted: PropTypes.bool, + onChange: PropTypes.func, + onSubmit: PropTypes.func, + onClear: PropTypes.func, + onShow: PropTypes.func, + intl: PropTypes.object, +}; diff --git a/app/javascript/flavours/glitch/features/compose/search/popout/index.js b/app/javascript/flavours/glitch/features/compose/search/popout/index.js new file mode 100644 index 000000000..fec090b64 --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/search/popout/index.js @@ -0,0 +1,109 @@ +// Package imports. +import PropTypes from 'prop-types'; +import React from 'react'; +import { + FormattedMessage, + defineMessages, +} from 'react-intl'; +import spring from 'react-motion/lib/spring'; + +// Utils. +import Motion from 'flavours/glitch/util/optional_motion'; +import { searchEnabled } from 'flavours/glitch/util/initial_state'; + +// Messages. +const messages = defineMessages({ + format: { + defaultMessage: 'Advanced search format', + id: 'search_popout.search_format', + }, + hashtag: { + defaultMessage: 'hashtag', + id: 'search_popout.tips.hashtag', + }, + status: { + defaultMessage: 'status', + id: 'search_popout.tips.status', + }, + text: { + defaultMessage: 'Simple text returns matching display names, usernames and hashtags', + id: 'search_popout.tips.text', + }, + full_text: { + defaultMessage: 'Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.', + id: 'search_popout.tips.full_text', + }, + user: { + defaultMessage: 'user', + id: 'search_popout.tips.user', + }, +}); + +// The spring used by our motion. +const motionSpring = spring(1, { damping: 35, stiffness: 400 }); + +// The component. +export default function DrawerSearchPopout ({ style }) { + + // The result. + return ( +
+ + {({ opacity, scaleX, scaleY }) => ( +
+

+
    +
  • + #example + {' '} + +
  • +
  • + @username@domain + {' '} + +
  • +
  • + URL + {' '} + +
  • +
  • + URL + {' '} + +
  • +
+ { searchEnabled ? : } +
+ )} +
+
+ ); +} + +// Props. +DrawerSearchPopout.propTypes = { style: PropTypes.object }; -- cgit