diff options
author | ThibG <thib@sitedethib.com> | 2018-12-18 19:37:07 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-12-18 19:37:07 +0100 |
commit | 74ee5bdf37799fba5990a6b492e3d52c32efe8d7 (patch) | |
tree | afdeb6b23ff4d7bd5e53c4e13cb2c3dfafb4b986 | |
parent | 36d27e289177fdec5332539c94b8192022a412f2 (diff) | |
parent | 0ef2c1415a13d305d4c73c71f27a1366eee702a0 (diff) |
Merge pull request #862 from ThibG/glitch-soc/merge-upstream
Merge upstream changes
81 files changed, 1367 insertions, 734 deletions
diff --git a/Dockerfile b/Dockerfile index 3e54b6555..826e237c4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:8.12.0-alpine as node +FROM node:8.14.0-alpine as node FROM ruby:2.4.5-alpine3.8 LABEL maintainer="https://github.com/tootsuite/mastodon" \ diff --git a/Gemfile b/Gemfile index ee2de58c9..63ea35a65 100644 --- a/Gemfile +++ b/Gemfile @@ -40,7 +40,7 @@ end gem 'net-ldap', '~> 0.10' gem 'omniauth-cas', '~> 1.1' gem 'omniauth-saml', '~> 1.10' -gem 'omniauth', '~> 1.2' +gem 'omniauth', '~> 1.9' gem 'doorkeeper', '~> 5.0' gem 'fast_blank', '~> 1.0' @@ -58,7 +58,7 @@ gem 'idn-ruby', require: 'idn' gem 'kaminari', '~> 1.1' gem 'link_header', '~> 0.0' gem 'mime-types', '~> 3.2', require: 'mime/types/columnar' -gem 'nokogiri', '~> 1.8' +gem 'nokogiri', '~> 1.9' gem 'nsa', '~> 0.2' gem 'oj', '~> 3.7' gem 'ostatus2', '~> 2.0' diff --git a/Gemfile.lock b/Gemfile.lock index a5dac694a..99db89693 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -251,7 +251,7 @@ GEM hamster (3.0.0) concurrent-ruby (~> 1.0) hashdiff (0.3.7) - hashie (3.5.7) + hashie (3.6.0) heapy (0.1.4) highline (2.0.0) hiredis (0.6.3) @@ -345,7 +345,7 @@ GEM mime-types-data (3.2018.0812) mimemagic (0.3.2) mini_mime (1.0.1) - mini_portile2 (2.3.0) + mini_portile2 (2.4.0) minitest (5.11.3) msgpack (1.2.4) multi_json (1.13.1) @@ -356,8 +356,8 @@ GEM net-ssh (>= 2.6.5) net-ssh (5.0.2) nio4r (2.3.1) - nokogiri (1.8.5) - mini_portile2 (~> 2.3.0) + nokogiri (1.9.1) + mini_portile2 (~> 2.4.0) nokogumbo (2.0.0) nokogiri (~> 1.8, >= 1.8.4) nsa (0.2.4) @@ -366,8 +366,8 @@ GEM sidekiq (>= 3.5.0) statsd-ruby (~> 1.2.0) oj (3.7.4) - omniauth (1.8.1) - hashie (>= 3.4.6, < 3.6.0) + omniauth (1.9.0) + hashie (>= 3.4.6, < 3.7.0) rack (>= 1.6.2, < 3) omniauth-cas (1.1.1) addressable (~> 2.3) @@ -712,10 +712,10 @@ DEPENDENCIES microformats (~> 4.0) mime-types (~> 3.2) net-ldap (~> 0.10) - nokogiri (~> 1.8) + nokogiri (~> 1.9) nsa (~> 0.2) oj (~> 3.7) - omniauth (~> 1.2) + omniauth (~> 1.9) omniauth-cas (~> 1.1) omniauth-saml (~> 1.10) ostatus2 (~> 2.0) diff --git a/app/controllers/admin/base_controller.rb b/app/controllers/admin/base_controller.rb index f2190ddf9..cc6cd51f0 100644 --- a/app/controllers/admin/base_controller.rb +++ b/app/controllers/admin/base_controller.rb @@ -20,5 +20,9 @@ module Admin def set_pack use_pack 'admin' end + + def set_user + @user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound) + end end end diff --git a/app/controllers/admin/confirmations_controller.rb b/app/controllers/admin/confirmations_controller.rb index 8d3477e66..efe7dcbd4 100644 --- a/app/controllers/admin/confirmations_controller.rb +++ b/app/controllers/admin/confirmations_controller.rb @@ -25,10 +25,6 @@ module Admin private - def set_user - @user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound) - end - def check_confirmation if @user.confirmed? flash[:error] = I18n.t('admin.accounts.resend_confirmation.already_confirmed') diff --git a/app/controllers/admin/resets_controller.rb b/app/controllers/admin/resets_controller.rb index 3e27d01ac..db8f61d64 100644 --- a/app/controllers/admin/resets_controller.rb +++ b/app/controllers/admin/resets_controller.rb @@ -10,11 +10,5 @@ module Admin log_action :reset_password, @user redirect_to admin_accounts_path end - - private - - def set_user - @user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound) - end end end diff --git a/app/controllers/admin/roles_controller.rb b/app/controllers/admin/roles_controller.rb index af7ec0740..13f56e9be 100644 --- a/app/controllers/admin/roles_controller.rb +++ b/app/controllers/admin/roles_controller.rb @@ -17,11 +17,5 @@ module Admin log_action :demote, @user redirect_to admin_account_path(@user.account_id) end - - private - - def set_user - @user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound) - end end end diff --git a/app/controllers/admin/two_factor_authentications_controller.rb b/app/controllers/admin/two_factor_authentications_controller.rb index 022107203..2577a4b17 100644 --- a/app/controllers/admin/two_factor_authentications_controller.rb +++ b/app/controllers/admin/two_factor_authentications_controller.rb @@ -2,7 +2,7 @@ module Admin class TwoFactorAuthenticationsController < BaseController - before_action :set_user + before_action :set_target_user def destroy authorize @user, :disable_2fa? @@ -13,7 +13,7 @@ module Admin private - def set_user + def set_target_user @user = User.find(params[:user_id]) end end diff --git a/app/controllers/directories_controller.rb b/app/controllers/directories_controller.rb index 1c8ebdac9..e91e784a5 100644 --- a/app/controllers/directories_controller.rb +++ b/app/controllers/directories_controller.rb @@ -37,22 +37,12 @@ class DirectoriesController < ApplicationController end def set_accounts - @accounts = Account.searchable.discoverable.page(params[:page]).per(50).tap do |query| + @accounts = Account.discoverable.page(params[:page]).per(30).tap do |query| query.merge!(Account.tagged_with(@tag.id)) if @tag - - if popular_requested? - query.merge!(Account.popular) - else - query.merge!(Account.by_recent_status) - end end end def set_instance_presenter @instance_presenter = InstancePresenter.new end - - def popular_requested? - request.path.ends_with?('/popular') - end end diff --git a/app/controllers/media_controller.rb b/app/controllers/media_controller.rb index 88c7232dd..8e1624ce1 100644 --- a/app/controllers/media_controller.rb +++ b/app/controllers/media_controller.rb @@ -6,12 +6,17 @@ class MediaController < ApplicationController before_action :set_media_attachment before_action :verify_permitted_status! + content_security_policy only: :player do |p| + p.frame_ancestors(false) + end + def show redirect_to @media_attachment.file.url(:original) end def player @body_classes = 'player' + response.headers['X-Frame-Options'] = 'ALLOWALL' raise ActiveRecord::RecordNotFound unless @media_attachment.video? || @media_attachment.gifv? end diff --git a/app/javascript/flavours/glitch/actions/notifications.js b/app/javascript/flavours/glitch/actions/notifications.js index 0184d9c80..3cfad90a1 100644 --- a/app/javascript/flavours/glitch/actions/notifications.js +++ b/app/javascript/flavours/glitch/actions/notifications.js @@ -2,6 +2,7 @@ import api, { getLinks } from 'flavours/glitch/util/api'; import IntlMessageFormat from 'intl-messageformat'; import { fetchRelationships } from './accounts'; import { defineMessages } from 'react-intl'; +import { List as ImmutableList } from 'immutable'; import { unescapeHTML } from 'flavours/glitch/util/html'; import { getFilters, regexFromFilters } from 'flavours/glitch/selectors'; @@ -22,6 +23,8 @@ export const NOTIFICATIONS_EXPAND_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST'; export const NOTIFICATIONS_EXPAND_SUCCESS = 'NOTIFICATIONS_EXPAND_SUCCESS'; export const NOTIFICATIONS_EXPAND_FAIL = 'NOTIFICATIONS_EXPAND_FAIL'; +export const NOTIFICATIONS_FILTER_SET = 'NOTIFICATIONS_FILTER_SET'; + export const NOTIFICATIONS_CLEAR = 'NOTIFICATIONS_CLEAR'; export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP'; @@ -84,10 +87,16 @@ export function updateNotifications(notification, intlMessages, intlLocale) { const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS(); +const excludeTypesFromFilter = filter => { + const allTypes = ImmutableList(['follow', 'favourite', 'reblog', 'mention']); + return allTypes.filterNot(item => item === filter).toJS(); +}; + const noOp = () => {}; export function expandNotifications({ maxId } = {}, done = noOp) { return (dispatch, getState) => { + const activeFilter = getState().getIn(['settings', 'notifications', 'quickFilter', 'active']); const notifications = getState().get('notifications'); const isLoadingMore = !!maxId; @@ -98,7 +107,9 @@ export function expandNotifications({ maxId } = {}, done = noOp) { const params = { max_id: maxId, - exclude_types: excludeTypesFromSettings(getState()), + exclude_types: activeFilter === 'all' + ? excludeTypesFromSettings(getState()) + : excludeTypesFromFilter(activeFilter), }; if (!maxId && notifications.get('items').size > 0) { @@ -244,3 +255,14 @@ export function notificationsSetVisibility(visibility) { visibility: visibility, }; }; + +export function setFilter (filterType) { + return dispatch => { + dispatch({ + type: NOTIFICATIONS_FILTER_SET, + path: ['notifications', 'quickFilter', 'active'], + value: filterType, + }); + dispatch(expandNotifications()); + }; +}; diff --git a/app/javascript/flavours/glitch/features/notifications/components/column_settings.js b/app/javascript/flavours/glitch/features/notifications/components/column_settings.js index d9638aaf3..4e35d5b4e 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/column_settings.js +++ b/app/javascript/flavours/glitch/features/notifications/components/column_settings.js @@ -21,9 +21,11 @@ export default class ColumnSettings extends React.PureComponent { render () { const { settings, pushSettings, onChange, onClear } = this.props; - const alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />; - const showStr = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />; - const soundStr = <FormattedMessage id='notifications.column_settings.sound' defaultMessage='Play sound' />; + const filterShowStr = <FormattedMessage id='notifications.column_settings.filter_bar.show' defaultMessage='Show' />; + const filterAdvancedStr = <FormattedMessage id='notifications.column_settings.filter_bar.advanced' defaultMessage='Display all categories' />; + const alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />; + const showStr = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />; + const soundStr = <FormattedMessage id='notifications.column_settings.sound' defaultMessage='Play sound' />; const showPushSettings = pushSettings.get('browserSupport') && pushSettings.get('isSubscribed'); const pushStr = showPushSettings && <FormattedMessage id='notifications.column_settings.push' defaultMessage='Push notifications' />; @@ -35,6 +37,16 @@ export default class ColumnSettings extends React.PureComponent { <ClearColumnButton onClick={onClear} /> </div> + <div role='group' aria-labelledby='notifications-filter-bar'> + <span id='notifications-filter-bar' className='column-settings__section'> + <FormattedMessage id='notifications.column_settings.filter_bar.category' defaultMessage='Quick filter bar' /> + </span> + <div className='column-settings__row'> + <SettingToggle id='show-filter-bar' prefix='notifications' settings={settings} settingPath={['quickFilter', 'show']} onChange={onChange} label={filterShowStr} /> + <SettingToggle id='show-filter-bar' prefix='notifications' settings={settings} settingPath={['quickFilter', 'advanced']} onChange={onChange} label={filterAdvancedStr} /> + </div> + </div> + <div role='group' aria-labelledby='notifications-follow'> <span id='notifications-follow' className='column-settings__section'><FormattedMessage id='notifications.column_settings.follow' defaultMessage='New followers:' /></span> diff --git a/app/javascript/flavours/glitch/features/notifications/components/filter_bar.js b/app/javascript/flavours/glitch/features/notifications/components/filter_bar.js new file mode 100644 index 000000000..f95a2c9de --- /dev/null +++ b/app/javascript/flavours/glitch/features/notifications/components/filter_bar.js @@ -0,0 +1,93 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; + +const tooltips = defineMessages({ + mentions: { id: 'notifications.filter.mentions', defaultMessage: 'Mentions' }, + favourites: { id: 'notifications.filter.favourites', defaultMessage: 'Favourites' }, + boosts: { id: 'notifications.filter.boosts', defaultMessage: 'Boosts' }, + follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' }, +}); + +export default @injectIntl +class FilterBar extends React.PureComponent { + + static propTypes = { + selectFilter: PropTypes.func.isRequired, + selectedFilter: PropTypes.string.isRequired, + advancedMode: PropTypes.bool.isRequired, + intl: PropTypes.object.isRequired, + }; + + onClick (notificationType) { + return () => this.props.selectFilter(notificationType); + } + + render () { + const { selectedFilter, advancedMode, intl } = this.props; + const renderedElement = !advancedMode ? ( + <div className='notification__filter-bar'> + <button + className={selectedFilter === 'all' ? 'active' : ''} + onClick={this.onClick('all')} + > + <FormattedMessage + id='notifications.filter.all' + defaultMessage='All' + /> + </button> + <button + className={selectedFilter === 'mention' ? 'active' : ''} + onClick={this.onClick('mention')} + > + <FormattedMessage + id='notifications.filter.mentions' + defaultMessage='Mentions' + /> + </button> + </div> + ) : ( + <div className='notification__filter-bar'> + <button + className={selectedFilter === 'all' ? 'active' : ''} + onClick={this.onClick('all')} + > + <FormattedMessage + id='notifications.filter.all' + defaultMessage='All' + /> + </button> + <button + className={selectedFilter === 'mention' ? 'active' : ''} + onClick={this.onClick('mention')} + title={intl.formatMessage(tooltips.mentions)} + > + <i className='fa fa-fw fa-at' /> + </button> + <button + className={selectedFilter === 'favourite' ? 'active' : ''} + onClick={this.onClick('favourite')} + title={intl.formatMessage(tooltips.favourites)} + > + <i className='fa fa-fw fa-star' /> + </button> + <button + className={selectedFilter === 'reblog' ? 'active' : ''} + onClick={this.onClick('reblog')} + title={intl.formatMessage(tooltips.boosts)} + > + <i className='fa fa-fw fa-retweet' /> + </button> + <button + className={selectedFilter === 'follow' ? 'active' : ''} + onClick={this.onClick('follow')} + title={intl.formatMessage(tooltips.follows)} + > + <i className='fa fa-fw fa-user-plus' /> + </button> + </div> + ); + return renderedElement; + } + +} diff --git a/app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js b/app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js index 9585ea556..4b863712a 100644 --- a/app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js +++ b/app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js @@ -2,6 +2,7 @@ import { connect } from 'react-redux'; import { defineMessages, injectIntl } from 'react-intl'; import ColumnSettings from '../components/column_settings'; import { changeSetting } from 'flavours/glitch/actions/settings'; +import { setFilter } from 'flavours/glitch/actions/notifications'; import { clearNotifications } from 'flavours/glitch/actions/notifications'; import { changeAlerts as changePushNotifications } from 'flavours/glitch/actions/push_notifications'; import { openModal } from 'flavours/glitch/actions/modal'; @@ -21,6 +22,9 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ onChange (path, checked) { if (path[0] === 'push') { dispatch(changePushNotifications(path.slice(1), checked)); + } else if (path[0] === 'quickFilter') { + dispatch(changeSetting(['notifications', ...path], checked)); + dispatch(setFilter('all')); } else { dispatch(changeSetting(['notifications', ...path], checked)); } diff --git a/app/javascript/flavours/glitch/features/notifications/containers/filter_bar_container.js b/app/javascript/flavours/glitch/features/notifications/containers/filter_bar_container.js new file mode 100644 index 000000000..4d495c290 --- /dev/null +++ b/app/javascript/flavours/glitch/features/notifications/containers/filter_bar_container.js @@ -0,0 +1,16 @@ +import { connect } from 'react-redux'; +import FilterBar from '../components/filter_bar'; +import { setFilter } from '../../../actions/notifications'; + +const makeMapStateToProps = state => ({ + selectedFilter: state.getIn(['settings', 'notifications', 'quickFilter', 'active']), + advancedMode: state.getIn(['settings', 'notifications', 'quickFilter', 'advanced']), +}); + +const mapDispatchToProps = (dispatch) => ({ + selectFilter (newActiveFilter) { + dispatch(setFilter(newActiveFilter)); + }, +}); + +export default connect(makeMapStateToProps, mapDispatchToProps)(FilterBar); diff --git a/app/javascript/flavours/glitch/features/notifications/index.js b/app/javascript/flavours/glitch/features/notifications/index.js index 0e73f02d8..6a149927c 100644 --- a/app/javascript/flavours/glitch/features/notifications/index.js +++ b/app/javascript/flavours/glitch/features/notifications/index.js @@ -15,6 +15,7 @@ import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/col import NotificationContainer from './containers/notification_container'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ColumnSettingsContainer from './containers/column_settings_container'; +import FilterBarContainer from './containers/filter_bar_container'; import { createSelector } from 'reselect'; import { List as ImmutableList } from 'immutable'; import { debounce } from 'lodash'; @@ -26,11 +27,22 @@ const messages = defineMessages({ }); const getNotifications = createSelector([ + state => state.getIn(['settings', 'notifications', 'quickFilter', 'show']), + state => state.getIn(['settings', 'notifications', 'quickFilter', 'active']), state => ImmutableList(state.getIn(['settings', 'notifications', 'shows']).filter(item => !item).keys()), state => state.getIn(['notifications', 'items']), -], (excludedTypes, notifications) => notifications.filterNot(item => item !== null && excludedTypes.includes(item.get('type')))); +], (showFilterBar, allowedType, excludedTypes, notifications) => { + if (!showFilterBar || allowedType === 'all') { + // used if user changed the notification settings after loading the notifications from the server + // otherwise a list of notifications will come pre-filtered from the backend + // we need to turn it off for FilterBar in order not to block ourselves from seeing a specific category + return notifications.filterNot(item => item !== null && excludedTypes.includes(item.get('type'))); + } + return notifications.filter(item => item !== null && allowedType === item.get('type')); +}); const mapStateToProps = state => ({ + showFilterBar: state.getIn(['settings', 'notifications', 'quickFilter', 'show']), notifications: getNotifications(state), localSettings: state.get('local_settings'), isLoading: state.getIn(['notifications', 'isLoading'], true), @@ -60,6 +72,7 @@ export default class Notifications extends React.PureComponent { static propTypes = { columnId: PropTypes.string, notifications: ImmutablePropTypes.list.isRequired, + showFilterBar: PropTypes.bool.isRequired, dispatch: PropTypes.func.isRequired, shouldUpdateScroll: PropTypes.func, intl: PropTypes.object.isRequired, @@ -151,12 +164,16 @@ export default class Notifications extends React.PureComponent { } render () { - const { intl, notifications, shouldUpdateScroll, isLoading, isUnread, columnId, multiColumn, hasMore } = this.props; + const { intl, notifications, shouldUpdateScroll, isLoading, isUnread, columnId, multiColumn, hasMore, showFilterBar } = this.props; const pinned = !!columnId; const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. Interact with others to start the conversation." />; let scrollableContent = null; + const filterBarContainer = showFilterBar + ? (<FilterBarContainer />) + : null; + if (isLoading && this.scrollableContent) { scrollableContent = this.scrollableContent; } else if (notifications.size > 0 || hasMore) { @@ -222,7 +239,7 @@ export default class Notifications extends React.PureComponent { > <ColumnSettingsContainer /> </ColumnHeader> - + {filterBarContainer} {scrollContainer} </Column> ); diff --git a/app/javascript/flavours/glitch/reducers/notifications.js b/app/javascript/flavours/glitch/reducers/notifications.js index b65c51f32..6667966c0 100644 --- a/app/javascript/flavours/glitch/reducers/notifications.js +++ b/app/javascript/flavours/glitch/reducers/notifications.js @@ -6,6 +6,7 @@ import { NOTIFICATIONS_EXPAND_SUCCESS, NOTIFICATIONS_EXPAND_REQUEST, NOTIFICATIONS_EXPAND_FAIL, + NOTIFICATIONS_FILTER_SET, NOTIFICATIONS_CLEAR, NOTIFICATIONS_SCROLL_TOP, NOTIFICATIONS_DELETE_MARKED_REQUEST, @@ -197,6 +198,8 @@ export default function notifications(state = initialState, action) { case NOTIFICATIONS_DELETE_MARKED_FAIL: case NOTIFICATIONS_EXPAND_FAIL: return state.set('isLoading', false); + case NOTIFICATIONS_FILTER_SET: + return state.set('items', ImmutableList()).set('hasMore', true); case NOTIFICATIONS_SCROLL_TOP: return updateTop(state, action.top); case NOTIFICATIONS_UPDATE: diff --git a/app/javascript/flavours/glitch/reducers/notifications.js.orig b/app/javascript/flavours/glitch/reducers/notifications.js.orig new file mode 100644 index 000000000..b65c51f32 --- /dev/null +++ b/app/javascript/flavours/glitch/reducers/notifications.js.orig @@ -0,0 +1,245 @@ +import { + NOTIFICATIONS_MOUNT, + NOTIFICATIONS_UNMOUNT, + NOTIFICATIONS_SET_VISIBILITY, + NOTIFICATIONS_UPDATE, + NOTIFICATIONS_EXPAND_SUCCESS, + NOTIFICATIONS_EXPAND_REQUEST, + NOTIFICATIONS_EXPAND_FAIL, + NOTIFICATIONS_CLEAR, + NOTIFICATIONS_SCROLL_TOP, + NOTIFICATIONS_DELETE_MARKED_REQUEST, + NOTIFICATIONS_DELETE_MARKED_SUCCESS, + NOTIFICATION_MARK_FOR_DELETE, + NOTIFICATIONS_DELETE_MARKED_FAIL, + NOTIFICATIONS_ENTER_CLEARING_MODE, + NOTIFICATIONS_MARK_ALL_FOR_DELETE, +} from 'flavours/glitch/actions/notifications'; +import { + ACCOUNT_BLOCK_SUCCESS, + ACCOUNT_MUTE_SUCCESS, +} from 'flavours/glitch/actions/accounts'; +import { TIMELINE_DELETE, TIMELINE_DISCONNECT } from 'flavours/glitch/actions/timelines'; +import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; +import compareId from 'flavours/glitch/util/compare_id'; + +const initialState = ImmutableMap({ + items: ImmutableList(), + hasMore: true, + top: true, + mounted: 0, + unread: 0, + lastReadId: '0', + isLoading: false, + cleaningMode: false, + isTabVisible: true, + // notification removal mark of new notifs loaded whilst cleaningMode is true. + markNewForDelete: false, +}); + +const notificationToMap = (state, notification) => ImmutableMap({ + id: notification.id, + type: notification.type, + account: notification.account.id, + markedForDelete: state.get('markNewForDelete'), + status: notification.status ? notification.status.id : null, +}); + +const normalizeNotification = (state, notification) => { + const top = !shouldCountUnreadNotifications(state); + + if (top) { + state = state.set('lastReadId', notification.id); + } else { + state = state.update('unread', unread => unread + 1); + } + + return state.update('items', list => { + if (top && list.size > 40) { + list = list.take(20); + } + + return list.unshift(notificationToMap(state, notification)); + }); +}; + +const expandNormalizedNotifications = (state, notifications, next) => { + const top = !(shouldCountUnreadNotifications(state)); + const lastReadId = state.get('lastReadId'); + let items = ImmutableList(); + + notifications.forEach((n, i) => { + items = items.set(i, notificationToMap(state, n)); + }); + + return state.withMutations(mutable => { + if (!items.isEmpty()) { + mutable.update('items', list => { + const lastIndex = 1 + list.findLastIndex( + item => item !== null && (compareId(item.get('id'), items.last().get('id')) > 0 || item.get('id') === items.last().get('id')) + ); + + const firstIndex = 1 + list.take(lastIndex).findLastIndex( + item => item !== null && compareId(item.get('id'), items.first().get('id')) > 0 + ); + + return list.take(firstIndex).concat(items, list.skip(lastIndex)); + }); + } + + if (top) { + if (!items.isEmpty()) { + mutable.update('lastReadId', id => compareId(id, items.first().get('id')) > 0 ? id : items.first().get('id')); + } + } else { + mutable.update('unread', unread => unread + items.filter(item => compareId(item.get('id'), lastReadId) > 0).size); + } + + if (!next) { + mutable.set('hasMore', false); + } + + mutable.set('isLoading', false); + }); +}; + +const filterNotifications = (state, relationship) => { + return state.update('items', list => list.filterNot(item => item !== null && item.get('account') === relationship.id)); +}; + +const clearUnread = (state) => { + state = state.set('unread', 0); + const lastNotification = state.get('items').find(item => item !== null); + return state.set('lastReadId', lastNotification ? lastNotification.get('id') : '0'); +} + +const updateTop = (state, top) => { + state = state.set('top', top); + + if (!shouldCountUnreadNotifications(state)) { + state = clearUnread(state); + } + + return state.set('top', top); +}; + +const deleteByStatus = (state, statusId) => { + const top = !(shouldCountUnreadNotifications(state)); + if (!top) { + const lastReadId = state.get('lastReadId'); + const deletedUnread = state.get('items').filter(item => item !== null && item.get('status') === statusId && compareId(item.get('id'), lastReadId) > 0); + state = state.update('unread', unread => unread - deletedUnread.size); + } + return state.update('items', list => list.filterNot(item => item !== null && item.get('status') === statusId)); +}; + +const markForDelete = (state, notificationId, yes) => { + return state.update('items', list => list.map(item => { + if(item.get('id') === notificationId) { + return item.set('markedForDelete', yes); + } else { + return item; + } + })); +}; + +const markAllForDelete = (state, yes) => { + return state.update('items', list => list.map(item => { + if(yes !== null) { + return item.set('markedForDelete', yes); + } else { + return item.set('markedForDelete', !item.get('markedForDelete')); + } + })); +}; + +const unmarkAllForDelete = (state) => { + return state.update('items', list => list.map(item => item.set('markedForDelete', false))); +}; + +const deleteMarkedNotifs = (state) => { + return state.update('items', list => list.filterNot(item => item.get('markedForDelete'))); +}; + +const updateMounted = (state) => { + state = state.update('mounted', count => count + 1); + if (!shouldCountUnreadNotifications(state)) { + state = clearUnread(state); + } + return state; +}; + +const updateVisibility = (state, visibility) => { + state = state.set('isTabVisible', visibility); + if (!shouldCountUnreadNotifications(state)) { + state = clearUnread(state); + } + return state; +}; + +const shouldCountUnreadNotifications = (state) => { + return !(state.get('isTabVisible') && state.get('top') && state.get('mounted') > 0); +}; + +export default function notifications(state = initialState, action) { + let st; + + switch(action.type) { + case NOTIFICATIONS_MOUNT: + return updateMounted(state); + case NOTIFICATIONS_UNMOUNT: + return state.update('mounted', count => count - 1); + case NOTIFICATIONS_SET_VISIBILITY: + return updateVisibility(state, action.visibility); + case NOTIFICATIONS_EXPAND_REQUEST: + case NOTIFICATIONS_DELETE_MARKED_REQUEST: + return state.set('isLoading', true); + case NOTIFICATIONS_DELETE_MARKED_FAIL: + case NOTIFICATIONS_EXPAND_FAIL: + return state.set('isLoading', false); + case NOTIFICATIONS_SCROLL_TOP: + return updateTop(state, action.top); + case NOTIFICATIONS_UPDATE: + return normalizeNotification(state, action.notification); + case NOTIFICATIONS_EXPAND_SUCCESS: + return expandNormalizedNotifications(state, action.notifications, action.next); + case ACCOUNT_BLOCK_SUCCESS: + case ACCOUNT_MUTE_SUCCESS: + return filterNotifications(state, action.relationship); + case NOTIFICATIONS_CLEAR: + return state.set('items', ImmutableList()).set('hasMore', false); + case TIMELINE_DELETE: + return deleteByStatus(state, action.id); + case TIMELINE_DISCONNECT: + return action.timeline === 'home' ? + state.update('items', items => items.first() ? items.unshift(null) : items) : + state; + + case NOTIFICATION_MARK_FOR_DELETE: + return markForDelete(state, action.id, action.yes); + + case NOTIFICATIONS_DELETE_MARKED_SUCCESS: + return deleteMarkedNotifs(state).set('isLoading', false); + + case NOTIFICATIONS_ENTER_CLEARING_MODE: + st = state.set('cleaningMode', action.yes); + if (!action.yes) { + return unmarkAllForDelete(st).set('markNewForDelete', false); + } else { + return st; + } + + case NOTIFICATIONS_MARK_ALL_FOR_DELETE: + st = state; + if (action.yes === null) { + // Toggle - this is a bit confusing, as it toggles the all-none mode + //st = st.set('markNewForDelete', !st.get('markNewForDelete')); + } else { + st = st.set('markNewForDelete', action.yes); + } + return markAllForDelete(st, action.yes); + + default: + return state; + } +}; diff --git a/app/javascript/flavours/glitch/reducers/settings.js b/app/javascript/flavours/glitch/reducers/settings.js index c04f262da..cb62f87b0 100644 --- a/app/javascript/flavours/glitch/reducers/settings.js +++ b/app/javascript/flavours/glitch/reducers/settings.js @@ -1,4 +1,5 @@ import { SETTING_CHANGE, SETTING_SAVE } from 'flavours/glitch/actions/settings'; +import { NOTIFICATIONS_FILTER_SET } from 'flavours/glitch/actions/notifications'; import { COLUMN_ADD, COLUMN_REMOVE, COLUMN_MOVE } from 'flavours/glitch/actions/columns'; import { STORE_HYDRATE } from 'flavours/glitch/actions/store'; import { EMOJI_USE } from 'flavours/glitch/actions/emojis'; @@ -34,6 +35,12 @@ const initialState = ImmutableMap({ mention: true, }), + quickFilter: ImmutableMap({ + active: 'all', + show: true, + advanced: false, + }), + shows: ImmutableMap({ follow: true, favourite: true, @@ -99,6 +106,7 @@ export default function settings(state = initialState, action) { switch(action.type) { case STORE_HYDRATE: return hydrate(state, action.state.get('settings')); + case NOTIFICATIONS_FILTER_SET: case SETTING_CHANGE: return state .setIn(action.path, action.value) diff --git a/app/javascript/flavours/glitch/styles/components/accounts.scss b/app/javascript/flavours/glitch/styles/components/accounts.scss index d87cd9c43..5f465259f 100644 --- a/app/javascript/flavours/glitch/styles/components/accounts.scss +++ b/app/javascript/flavours/glitch/styles/components/accounts.scss @@ -445,12 +445,19 @@ } } +.notification__filter-bar, .account__section-headline { background: darken($ui-base-color, 4%); border-bottom: 1px solid lighten($ui-base-color, 8%); cursor: default; display: flex; + button { + background: darken($ui-base-color, 4%); + border: 0; + } + + button, a { display: block; flex: 1 1 auto; diff --git a/app/javascript/flavours/glitch/styles/containers.scss b/app/javascript/flavours/glitch/styles/containers.scss index 398458e47..82d4050d7 100644 --- a/app/javascript/flavours/glitch/styles/containers.scss +++ b/app/javascript/flavours/glitch/styles/containers.scss @@ -296,6 +296,12 @@ text-decoration: underline; color: $primary-text-color; } + + @media screen and (max-width: $no-gap-breakpoint) { + &.optional { + display: none; + } + } } .nav-button { diff --git a/app/javascript/flavours/glitch/styles/widgets.scss b/app/javascript/flavours/glitch/styles/widgets.scss index c863e3b4f..87e633c70 100644 --- a/app/javascript/flavours/glitch/styles/widgets.scss +++ b/app/javascript/flavours/glitch/styles/widgets.scss @@ -229,18 +229,6 @@ margin-bottom: 10px; } -.moved-account-widget, -.memoriam-widget, -.box-widget, -.contact-widget, -.landing-page__information.contact-widget { - @media screen and (max-width: $no-gap-breakpoint) { - margin-bottom: 0; - box-shadow: none; - border-radius: 0; - } -} - .page-header { background: lighten($ui-base-color, 8%); box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); @@ -261,11 +249,20 @@ font-size: 15px; color: $darker-text-color; } + + @media screen and (max-width: $no-gap-breakpoint) { + margin-top: 0; + background: lighten($ui-base-color, 4%); + + h1 { + font-size: 24px; + } + } } .directory { background: $ui-base-color; - border-radius: 0 0 4px 4px; + border-radius: 4px; box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); &__tag { @@ -407,4 +404,24 @@ font-size: 14px; } } + + @media screen and (max-width: $no-gap-breakpoint) { + tbody td.optional { + display: none; + } + } +} + +.moved-account-widget, +.memoriam-widget, +.box-widget, +.contact-widget, +.landing-page__information.contact-widget, +.directory, +.page-header { + @media screen and (max-width: $no-gap-breakpoint) { + margin-bottom: 0; + box-shadow: none; + border-radius: 0; + } } diff --git a/app/javascript/images/screen_federation.svg b/app/javascript/images/screen_federation.svg new file mode 100644 index 000000000..7019a7356 --- /dev/null +++ b/app/javascript/images/screen_federation.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="806.09998" width="1197.7164" viewBox="0 0 1197.7164 806.09999" id="Layer_1"><path id="path6078" d="M482.4164 557.4c-42.3-31.3-89.9-55.7-141.2-72.5-53.2-17.4-109.1-26.2-166.3-26.2-59.7 0-117.9 9.6-173.1 28.5v315.9h645.6c-21.6-95.7-80.1-182.9-165-245.7z" class="st33" fill="#fff"/><path stroke-miterlimit="10" id="path6082" d="M515.7164 589.8c-1.1 3.3-6.4 5.1-7.4 4.2l-.2-7.1.2-3.6c-86.1-74.6-203.7-120.6-333.4-120.6-61 0-119.4 10.2-173.1 28.8v313.6h641.9c-18.4-83.2-63.7-157.2-128-215.3" class="st80" fill="#587faa" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6084" d="M351.5164 565.1l9.3 4.8" class="st81" fill="none" stroke="#2d3f68" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6086" d="M336.5164 558c2.2 1 4.4 2 6.5 3" class="st81" fill="none" stroke="#2d3f68" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6088" d="M321.5164 551.5c2.6 1 5.1 2.1 7.7 3.2" class="st81" fill="none" stroke="#2d3f68" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6090" d="M304.8164 545.1c3.4 1.2 6.7 2.5 10 3.7" class="st81" fill="none" stroke="#2d3f68" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/><g id="g6092" transform="translate(-1343.3836 -75.800003)"><path stroke-miterlimit="10" id="path6094" d="M1890.4 836.8c1.9 3.9 3.8 7.8 5.5 11.7" class="st81" fill="none" stroke="#2d3f68" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6100" d="M1880.9 818.7c1.5 2.7 2.9 5.4 4.4 8.1" class="st81" fill="none" stroke="#2d3f68" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6102" d="M1871.4 802.4c1.8 2.9 3.5 5.8 5.2 8.8" class="st81" fill="none" stroke="#2d3f68" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6104" d="M1860.3 785.1c2 2.9 4 5.9 5.9 8.9" class="st81" fill="none" stroke="#2d3f68" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6106" d="M1848.4 768.6c2.2 2.9 4.3 5.8 6.4 8.8" class="st81" fill="none" stroke="#2d3f68" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6108" d="M1834.4 751c2.6 3.1 5.2 6.3 7.7 9.5" class="st81" fill="none" stroke="#2d3f68" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></g><path id="path6110" d="M156.6164 523.5c-54.6 0-106.7 10.1-154.8 28.4V805h559.5c-59.5-164.1-218.2-281.5-404.7-281.5z" class="st82" fill="#2d3f68"/><g id="g6112" transform="translate(-1343.3836 -75.800003)"><path id="path6114" d="M2065.2 128.3c1.4 69-36.9 80.4-35.7 141.1 1.2 60.6 39.9 70.5 41.3 139.5 1.4 69-36.9 80.4-35.7 141.1 1.2 60.6 39.9 70.5 41.3 139.5 1.4 69-36.9 80.4-35.7 141.1.4 20.5 5.1 35.2 11.2 48.3H2541v-803h-487.3c6.2 13.6 11 29.7 11.5 52.4z"/></g><g id="g6116" transform="translate(-1343.3836 -75.800003)"><circle stroke-miterlimit="10" id="circle6118" r="33.799999" cy="645.40002" cx="2236.6001" class="st83" fill="#ebb548" stroke="#663f09" stroke-width="3.30660009" stroke-linecap="round" stroke-linejoin="round"/><g id="g6120"><path stroke-miterlimit="10" id="path6122" d="M2243.3 641.5c-2.4-5.6-8-6.2-15.8-3.2-5.7 2.2-18.2 7.3-24.5 9.9.7 8.2 4.3 15.5 9.7 21 6.7-2.6 21.3-8.2 32-12.6 14.3-5.9 12.9-9.1 10.2-14.1-2.7-5-11.6-1-11.6-1z" class="st84" fill="#ad6a3b" stroke="#663f09" stroke-width="1.65330005"/><path stroke-miterlimit="10" id="path6124" d="M2243.4 612.3c-12.6 4.4-13.1 7.3-11.2 11.6 2.1 4.6 9.4 2.6 15.4.1 2.6-1 6.8-2.8 10.7-4.5-4.2-3.5-9.2-6.1-14.9-7.2z" class="st84" fill="#ad6a3b" stroke="#663f09" stroke-width="1.65330005"/></g><g id="g6126"><circle id="circle6128" r="3" cy="627.70001" cx="2218.8" class="st33" fill="#fff"/></g></g><g id="g6130" transform="translate(-1343.3836 -75.800003)"><circle stroke-miterlimit="10" id="circle6132" r="33.799999" cy="485.79999" cx="2402.5" class="st83" fill="#ebb548" stroke="#663f09" stroke-width="3.30660009" stroke-linecap="round" stroke-linejoin="round"/><g id="g6134"><circle id="circle6136" r="3" cy="468.20001" cx="2384.8" class="st33" fill="#fff"/></g></g><path id="path6138" d="M292.3164 506.3c-2.7 0-6.7-.4-9.8-3.5-2.9-3-4.2-7.6-4-14 .1-2.2.2-4.1.2-5.9-2.9-.4-5.8-.9-8.6-1.7-17.4-4.7-31.5-16.3-39.6-32.8-1-2.1-2-4.2-2.8-6.4-.6 0-1.2 0-1.7.1-.9 2.2-1.8 4.3-2.8 6.4-8.1 16.5-22.1 28.1-39.6 32.8-2.8.8-5.7 1.3-8.6 1.7.1 1.7.1 3.7.2 5.9.3 6.4-1 11-4 14-3.1 3.2-7.1 3.5-9.8 3.5H149.6164c-13.1 0-13.1-9.7-13.1-14.9v-14.7c-2-.9-3.9-1.9-5.7-3-1.6 2.7-4.5 5.6-9.3 6.9-4.8 1.2-8.9 1.8-12.5 1.8-4.5 0-8.3-1-11.2-2.9-3.3-2.2-5.4-5.7-6-10.1-1.5-10.5-.6-17.6 2.6-21.8 1.8-2.3 4.2-3.6 7.1-3.9l5.5-.5c-9.9-1.8-15.4-7.7-16.4-17.5-.8-8.1 6.6-16.9 11-18.6 1.1-.5 2.2-.7 3.1-.7 1.8 0 3.2.8 4.2 2.2 1 1.4 1.9 4.3-2.2 8.4-.7.9-3.3 6.5-1.8 9.9.8 1.7 2.7 2.7 5.8 3.1 1.9-7.1 5.8-12.8 11.4-16.6h-.5c-3.8 0-7-1.3-9.2-3.8-2.2-2.5-3.2-6-2.7-9.9.6-4.5.6-11.5 0-21.9-.7-13.3 4.2-23.4 13-26.3 2.2-.7 4.4-1.1 6.6-1.1 3.2 0 6.4.8 9.3 2.2l.1-.1c-1.5-3.1-1.9-6.5-1.1-9.5.7-2.6 3.4-8.9 13.8-10.1 1.4-.2 3.5-.3 6.1-.3 2.1-3.3 5.4-7.6 9.2-9.8.1-.7.1-1.2.2-1.7-1.3-3-1.4-5.7-.3-8 .7-1.6 2.4-3.7 6-4.6.7-.2 1.5-.3 2.2-.3 2.9 0 5.6 1.4 7.7 4 .4-.1.7-.1 1.1-.1h.5c2.6.2 5 1.5 6.6 3.8 2.2 3 1.3 7.1-2 10.5.3 3.1 1.5 5.7 3.5 7.7.5.5 1.2 1 1.9 1.5 4.7.7 8.8 1.6 12.5 2.5 2.8.7 5.3 1.2 7.1 1.4 1.3.2 2.8.2 4.4.2 7.5 0 17.4-1.7 22.8-6.5 2.2-1.9 3.4-4.2 3.8-6.9-2.8-2.9-3.4-6.6-1.6-9.6.6-1 2.4-4 5.8-4.6.4-.1.8-.1 1.2-.1.4 0 .8 0 1.2.1 2-2 4.2-3.1 6.5-3.1.8 0 1.6.1 2.4.4 2.5.8 4.6 2.6 5.7 4.9.9 1.9 1.1 4.1.5 6.3.3 2.4.9 7.8.1 14.5 7.9.2 15.7 2.7 23.7 7.6 1.5.9 2.8 1.8 4.1 2.7.9-.3 1.9-.6 3-.8 1.6-.3 3.2-.5 4.7-.5 7 0 16.4 3.6 21.7 20.7 1.4 4.7 2.9 8.2 4 11.1 2.7 6.6 4.8 11.9-1.1 17.1-3.9 3.4-7.7 5.2-11.5 5.2-1.3 0-2.5-.2-3.7-.6v.1c16.3 3.4 27.2 14.7 31.3 32.1 3.4-.3 5.5-1.4 6.3-3.2 1.5-3.4-1-9-1.9-10-4-4-3-6.9-2.1-8.3.9-1.4 2.4-2.2 4.2-2.2 1 0 2 .2 3.1.7 4.3 1.7 11.8 10.6 11 18.6-1 9.8-6.5 15.7-16.5 17.5l5.5.6c2.8.3 5.3 1.6 7.1 3.9 3.3 4.2 4.1 11.3 2.6 21.8-.6 4.4-2.7 7.9-6 10.1-2.9 1.9-6.6 2.9-11.1 2.9-3.6 0-7.7-.6-12.5-1.8-4.8-1.2-7.7-4.2-9.3-6.9-1.9 1.1-3.8 2.1-5.7 3v14.7c0 5.2 0 14.9-13.1 14.9h-10.8c-.5.1-.8.1-1.2.1z" class="st33" fill="#fff"/><g id="g6140" transform="translate(-1343.3836 -75.800003)"><path stroke-miterlimit="10" id="path6142" d="M1527.8 400.3s4.8-3.7 2.9-6.3c-1.9-2.6-5-2.5-6.5-.9 0 0-2.4-6.1-7.2-4.9-3.7.9-4.6 3.7-2.4 7.9 0 0-8.4 42.4 35.2 53.1l4.6-29.6c-11.7.1-26.6-3.8-26.6-19.3z" class="st85" fill="#fbc16c" stroke="#946f3a" stroke-width="2.38770008" stroke-linecap="round" stroke-linejoin="round"/><g id="g6144"><path stroke-miterlimit="10" id="path6146" d="M1695.3 523.5c-6-.6-11.9-1.2-11.9-1.2s3.6-40.6-32.8-45.4c.6-34.6-2.4-44.8-19.1-54.9-23.7-14.4-38.7-2.2-48.8-1-1.3.2-2.9.3-4.7.3l-7.6 27.9s-9.3 39.1 7.5 73.1c16.7 34 58.5 42.4 90.7 20.9 0 0 1.2 7.2 8.4 9 7.2 1.8 22.7 4.8 24.5-7.8 1.6-12.5-.2-20.3-6.2-20.9z" class="st86" fill="#876a4d" stroke="#3b3024" stroke-width="2.38770008" stroke-linecap="round" stroke-linejoin="round"/></g><path stroke-miterlimit="10" id="path6148" d="M1474.2 489.2c-.6-34.6 0-56.7 25.1-68.6 25.1-11.9 47.2-2.4 57.3-1.2 10.1 1.2 35.8-1.2 35.8-19.1 0 0-4.1-2.8-2.3-5.6 1.7-2.8 3.4-3.6 5.9-1.6 0 0 2.7-5.1 6.3-3.9 2.5.8 4.6 3.3 3.2 6.9 0 0 8.4 42.4-35.2 53.1 0 0 9.3 39.1-7.5 73.1-16.7 34-58.5 42.4-90.7 20.9 0 0-1.2 7.2-8.4 9-7.2 1.8-22.7 4.8-24.5-7.8-1.8-12.5 0-20.3 6-20.9 6-.6 11.9-1.2 11.9-1.2s-4.7-25.6 17.1-33.1z" class="st85" fill="#fbc16c" stroke="#946f3a" stroke-width="2.38770008" stroke-linecap="round" stroke-linejoin="round"/><path id="path6150" d="M1510 553.9c-13 0-25.9-4-37.2-11.6l-1.5-1-.3 1.8c0 .3-1.2 6.4-7.5 8-4.4 1.1-8 1.6-11.1 1.6-7.2 0-11.1-2.7-11.9-8.4-1.6-11 0-15.7 1.6-17.7.9-1.1 2-1.7 3.4-1.8l13.2-1.3-.2-1.3c0-.2-2.4-13.5 4.5-23.2-.7 4.7.2 9.5 2.6 13.6 2.8 4.7 8.9 10.7 22.1 12.4 4.2.5 8.4.9 12.6.8 40.6-1 51.6-8.4 53.3-27l15.7 1.5c-1.7 7.8-4.1 15.1-7.4 21.7-10 19.9-29.3 31.9-51.9 31.9z" class="st4" fill="#e09c5c"/><path id="path6152" d="M1630.6 553.9c13 0 25.9-4 37.2-11.6l1.5-1 .3 1.8c0 .3 1.2 6.4 7.5 8 4.4 1.1 8 1.6 11.1 1.6 7.2 0 11.1-2.7 11.9-8.4 1.6-11 0-15.7-1.6-17.7-.9-1.1-2-1.7-3.4-1.8l-13.2-1.3.2-1.3c0-.2 2.4-13.5-4.5-23.2.7 4.7-.2 9.5-2.6 13.6-2.8 4.7-8.9 10.7-22.1 12.4-4.2.5-8.4.9-12.6.8-40.6-1-51.6-8.4-53.3-27l-15.7 1.5c1.7 7.8 4.1 15.1 7.4 21.7 10 19.9 29.3 31.9 51.9 31.9z" fill="#6e5a3d"/><path stroke-miterlimit="10" id="path6154" d="M1484 544.4v22.7c0 6.6.6 10.7 9 10.7h10.1c3.5 0 11.9 1.2 11.3-13.1-.6-14.3-.6-19.1-.6-19.1" class="st87" fill="#e09c5c" stroke="#946f3a" stroke-width="2.38770008" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6160" d="M1460.2 515.2c-13.7.6-21-3.6-22.1-14.3-.6-6 5.4-13.1 8.4-14.3 3-1.2 3 .6.6 3s-10.7 21.5 11.9 20.3" class="st87" fill="#e09c5c" stroke="#946f3a" stroke-width="2.38770008" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6162" d="M1581.8 460.8c-.1-.8-.4-1.7.2-2.3.6-.6 1.1-.5 2-.5 5.8.1 21.1.2 24 .4 3.6.1 3.1 2.9 1.6 6-3.9 7.6-6.4 11.5-14.2 11.4-6.5-.1-12.4-6.2-13.6-15z" fill="#33281d" stroke="#3b3024" stroke-width="2.38770008" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6164" d="M1609.5 464.4c-3.9 7.6-6.4 11.5-14.2 11.4-1 0-2-.2-3-.5-.1-.9-.2-1.7-.2-2.6 0-5.6 2.2-10.7 5.8-14.5 4.5.1 8.6.1 9.9.2 3.7.1 3.3 2.9 1.7 6z" fill="#693131" stroke="#3b3024" stroke-width="2.38770008" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6166" d="M1680.4 515.2c13.7.6 21-3.6 22.1-14.3.6-6-5.4-13.1-8.4-14.3-3-1.2-3 .6-.6 3s10.7 21.5-11.9 20.3" class="st86" fill="#876a4d" stroke="#3b3024" stroke-width="2.38770008" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6168" d="M1488.2 439.4c-5.4-8.4-13.7-10.7-20.9-8.4-7.2 2.4-10.7 11.3-10.1 22.1.6 10.7.6 17.9 0 22.7-.6 4.8 1.8 9 7.8 9s7.2-4.2 7.2-4.2 1.8 2.4 5.4 2.4c3.6 0 10.1-2.4 10.7-10.1.6-7.7.6-9 .6-9" class="st85" fill="#fbc16c" stroke="#946f3a" stroke-width="2.38770008" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6170" d="M1595 506.2c-6.8 6.9-23.1 8.1-33.8 7.3-10.7-.8-8.5-7.1-8.5-16.1 0-5.5 4.3-6.5 10.8-7.3 6.5-.8 19-.4 25.2-4.8" class="st86" fill="#876a4d" stroke="#3b3024" stroke-width="2.38770008" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6172" d="M1548.5 478.8c14.9 0 23.9-1.8 29.2-2.4 5.4-.6 13.1 1.2 13.1 9.6 0 9.6-3.6 12.5-14.3 14.3-10.7 1.8-21.5 1.8-30.4 1.2" class="st85" fill="#fbc16c" stroke="#946f3a" stroke-width="2.38770008" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6174" d="M1623.7 467.4c-8.1 8.1-15.5 9-23.9 6-8.4-3-10.7-10.1-10.1-15.5.6-5.4 4.2-6 6-1.2 1.8 4.8 3 7.8 10.7 9 4.2.6 7.2-2.4 10.7-5.4 3.6-3 6-3 7.8-.6 2.1 2.8 1.2 5.3-1.2 7.7z" class="st91" fill="#fff" stroke="#7d7d65" stroke-width="2.38770008" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6176" d="M1515.1 456.1s-2.4-6-3.6-10.1c-1.2-4.2-1.8-7.2 1.8-8.4 3.6-1.2 6 1.2 7.8 5.4 1.8 4.2 3.6 10.1 3.6 10.1" class="st92" fill="none" stroke="#402f19" stroke-width="2.98460007" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6178" d="M1557 460.8c.1-.8.4-1.7-.2-2.3-.6-.6-1.1-.5-2-.5-5.8.1-21.1.2-24 .4-3.6.1-3.1 2.9-1.6 6 3.9 7.6 6.4 11.5 14.2 11.4 6.5-.1 12.5-6.2 13.6-15z" fill="#544024" stroke="#946f3a" stroke-width="2.38770008" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6180" d="M1632.1 440c-1.8-7.8 1.2-13.7 9.6-15.5 8.4-1.8 16.7 1.8 21.5 17.3 4.8 15.5 9.6 19.1 4.2 23.9-5.4 4.8-10.1 5.4-14.9 1.8 0 0-4.2 4.2-8.4.6s-4.2-7.8-4.2-7.8" class="st86" fill="#876a4d" stroke="#3b3024" stroke-width="2.38770008" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6182" d="M1605.4 452c0-6.9.7-17.9 6-18.5 5.3-.6 6.9 2.8 6.4 8.8-.5 6-1 11.9-1 11.9" class="st92" fill="none" stroke="#402f19" stroke-width="2.98460007" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6184" d="M1529.3 464.4c3.9 7.6 6.4 11.5 14.2 11.4 1 0 2-.2 3-.5.1-.9.2-1.7.2-2.6 0-5.6-2.2-10.7-5.8-14.5-4.5.1-8.6.1-9.9.2-3.7.1-3.3 2.9-1.7 6z" fill="#693131" stroke="#381916" stroke-width="2.38770008" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6186" d="M1557 460.8c.1-.8.4-1.7-.2-2.3-.6-.6-1.1-.5-2-.5-5.8.1-21.1.2-24 .4-3.6.1-3.1 2.9-1.6 6 3.9 7.6 6.4 11.5 14.2 11.4 6.5-.1 12.5-6.2 13.6-15z" fill="none" stroke="#3b3024" stroke-width="2.38770008" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6188" d="M1504.9 467.4c8.1 8.1 15.5 9 23.9 6 8.4-3 10.7-10.1 10.1-15.5-.6-5.4-4.2-6-6-1.2-1.8 4.8-3 7.8-10.7 9-4.2.6-7.2-2.4-10.7-5.4-3.6-3-6-3-7.8-.6-2.1 2.8-1.2 5.3 1.2 7.7z" class="st91" fill="#fff" stroke="#7d7d65" stroke-width="2.38770008" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6190" d="M1490 431.3c-7.1-5.2-8.4-17 5.3-18.6 2.7-.3 8-.2 8-.2s6.4-11.5 11.7-11c5.3.5 5.5 5.2 4.3 7.5 0 0 5.9.2 4.4 6.9-.6 2.8-3.6 4.2-3.6 4.2" class="st85" fill="#fbc16c" stroke="#946f3a" stroke-width="2.38770008" stroke-linecap="round" stroke-linejoin="round"/><path id="path6192" d="M1499.9 459.7l-3.7 1.4c-.9.4-2-.1-2.3-1l-.1-.3c-.4-.9.1-2 1-2.3l3.7-1.4c.9-.4 2 .1 2.3 1l.1.3c.4.9-.1 1.9-1 2.3z" class="st31" fill="#e68a4c"/><path id="path6194" d="M1630.2 459.7l3.7 1.4c.9.4 2-.1 2.3-1l.1-.3c.4-.9-.1-2-1-2.3l-3.7-1.4c-.9-.4-2 .1-2.3 1l-.1.3c-.4.9.1 1.9 1 2.3z" fill="#805945"/><path stroke-miterlimit="10" id="path6196" d="M1592.7 412.4c.3-.6.7-1.3.9-2" class="st96" fill="none" stroke="#fbd7a3" stroke-width="2.38770008" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6198" d="M1588.8 417.1c.8-.7 1-.7 1.7-1.6" class="st96" fill="none" stroke="#fbd7a3" stroke-width="2.38770008" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6200" d="M1576.3 423c2.5-.6 4.9-1.4 7-2.4" class="st96" fill="none" stroke="#fbd7a3" stroke-width="2.38770008" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6202" d="M1560.5 424.8c1.7.1 3.5 0 5.4-.2 1.3-.1 2.6-.3 3.9-.5" class="st96" fill="none" stroke="#fbd7a3" stroke-width="2.38770008" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6204" d="M1533 419.3c6.5.6 11.4 2.2 16.3 3.6" class="st96" fill="none" stroke="#fbd7a3" stroke-width="2.38770008" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6206" d="M1627.4 425.2c.7.5 1.4.9 2.1 1.5" class="st97" fill="none" stroke="#b58e67" stroke-width="2.38770008" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6208" d="M1621.5 422.2c.9.4 1.9.8 2.8 1.3" class="st97" fill="none" stroke="#b58e67" stroke-width="2.38770008" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6210" d="M1607.9 419.7c2.4.1 4.8.3 7.3.7" class="st97" fill="none" stroke="#b58e67" stroke-width="2.38770008" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6212" d="M1656.5 544.4v22.7c0 6.6-.6 10.7-9 10.7h-10.1c-3.5 0-11.9 1.2-11.3-13.1.6-14.3.6-19.1.6-19.1" fill="#6e5a3d" stroke="#3b3024" stroke-width="2.38770008" stroke-linecap="round" stroke-linejoin="round"/></g><g id="g6532" transform="translate(-1343.3836 -75.800003)"><circle stroke-miterlimit="10" id="circle6534" r="103.3" cy="500.79999" cx="1949" fill="#4a77ab" stroke="#132137" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/><circle id="circle6536" r="74.699997" cy="500.79999" cx="1949" class="st16" fill="#51a1b5"/><g id="g6538"><path stroke-miterlimit="10" id="path6540" d="M2005.2 459.7c3.8 9.9 14.9 9.6 29.9 4.9 3.4-1.1 6.5-2.1 9.3-3.1-3.4-8.3-7.9-16-13.2-23-18.9 5.2-29.4 12.3-26 21.2z" fill="#75bd8c" stroke="#132137" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6542" d="M2017 544.7c-5.9 11 .3 18.8 10.9 22.7 7.6-9 13.7-19.4 17.9-30.6-10.8-.8-24.5-.1-28.8 7.9z" class="st140" fill="#e0b779" stroke="#132137" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6544" d="M2003.9 420.2c.4-2.2.5-4.4.5-6.5-17.8-11.3-39.3-17.3-62-15.9-19 1.2-36.5 7.5-51.2 17.5-.4 12.8 2.4 21.8 13.8 25 15.7 4.4 26.4-.3 26.4-.3s2.1 12.3 23 13.1c20.9.8 20.9-14.8 20.9-14.8s25 3.3 28.6-18.1z" class="st140" fill="#e0b779" stroke="#132137" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></g><path stroke-miterlimit="10" id="path6546" d="M2001.9 491.9c.4 8.1-3.7 14-13.6 17.7-9.8 3.8-20 4.2-32-6.9-12-11.1-38.3-23.9-61-20.5-28.8 4.3-36.6 23.5-36.6 23.5-1.9-2.2-5.5-6.1-12.9-9.8-.2 3.8-.1 7.6.1 11.4 3.6 56.9 52.7 100.1 109.7 96.5 6.3-.4 12.4-1.4 18.3-2.8-2.6-39.5-19.3-67.9-19.3-67.9 13.7 5.7 27.8 5.7 45.9-2.8 18.1-8.5 23.6-24.6 20.4-38-3.3-13.4-19.3-8.5-19-.4z" fill="#75bd8c" stroke="#1c3b28" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/><circle stroke-miterlimit="10" id="circle6548" r="9" cy="510.89999" cx="1898.9" class="st142" fill="#51a1b5" stroke="#1c3b28" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><circle id="circle6550" r="9" cy="479.5" cx="1970.5" fill="#4a77ab"/><path stroke-miterlimit="10" id="path6552" d="M1879.4 530.7c-2.2 4.6 0 11.8 10.8 14.3s24.7-.1 30.8-6.5c6.5-6.9 8.8-13.5 3.5-17.3-5.3-3.8-11.3.4-14.3 4.7s-10.1 4.7-17.7 1.8c-7.5-2.9-10.5-2.6-13.1 3z" class="st142" fill="#51a1b5" stroke="#1c3b28" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6554" d="M1956.1 381.4c8.2-3 11.7-4.5 14.4-5.7 2.7-1.2 5-.4 6.4 2.4 1.3 2.8-.8 5.7-6 7.7-5.3 2-13 5-13 5" class="st25" fill="#fbc16c" stroke="#946f3a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6556" d="M1950.6 411.4c.1 5.7-1.9 11.2 4.1 11.3 5.9.1 6.2-3.1 6.2-5.9 0-2.8-.1-4.7-.1-4.7" class="st26" fill="#e09c5c" stroke="#946f3a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6558" d="M1913.2 410.2c4.9 3.8 12.6 2.5 14.2.1 1.6-2.4-.4-3.4-2.3-2.3s-7.2 1.6-9.7.2c-2.5-1.3-3.8.8-2.2 2z" class="st25" fill="#fbc16c" stroke="#946f3a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6560" d="M1921.1 396c0 10.2 2.3 18.5 12 19s17.9-.1 23.8-.9c5.9-.8 11.1-3.5 10.9-13.7-.1-10.2-.1-25.2-.1-25.2s8.4 3 15.6-.8c7.7-4 9.7-6.7 11-8.8 1.1-1.9-1.2-7.9-6.1-4.1-4.9 3.8-10.1 6.1-15.2 3.9-5.1-2.2-7.2-5-9.9-9.3-2.5-3.9-7.7-8.1-20.6-7.7-14 .5-21.9 13-21.6 25.1.3 12.1.2 22.5.2 22.5z" class="st25" fill="#fbc16c" stroke="#946f3a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path id="path6562" d="M1939.7 414.1c-2.2 0-4.4-.1-6.6-.1-7.6-.3-11-5.9-11-18v-5.8c6.6-.2 11.3 3.5 11.6 8 .5 7.5 6.8 10 15.7 10h5.2c4 0 8.7 0 11.6-2.2-1.6 5.2-5.7 6.5-9.4 7-3.8.6-9.8 1.1-17.1 1.1z" class="st4" fill="#e09c5c"/><path stroke-miterlimit="10" id="path6564" d="M1929.4 357.9c-2.1-7-10.8-9.6-15.6-3.8-4.8 5.8-9.9 17.9-6.3 21.1 3.6 3.2 5.2 2.3 7.1 1.5 0 0 2.3 4.1 6.9.9 4.5-3.2 6-9.6 6-9.6" class="st25" fill="#fbc16c" stroke="#946f3a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6566" d="M1938 378c5.7 4.2 11.2 4.4 14.7 1.8 3.4-2.5 5.6-5.2 4-6.9-1.6-1.7-3.5-.1-5.5 1.3-2 1.4-5.1 1.8-7.9.1-2.8-1.7-5-2.2-6.3-.8-1.4 1.5-.5 3.4 1 4.5z" fill="#fbe6c6" stroke="#668794" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path id="path6568" d="M1940.7 364.8c0 3.8-.2 4.2-1.2 4.2s-1.3-.5-1.3-4.2c0-4.6.3-5.1 1.3-5.1s1.2.6 1.2 5.1z"/><path stroke-miterlimit="10" id="path6570" d="M1926 380.7c-8.2-3-8.1-3-10.7-4.2-2.6-1.2-5-.4-6.4 2.4-1.3 2.8.8 5.7 6 7.7 5.3 2 9.4 3.5 9.4 3.5" class="st25" fill="#fbc16c" stroke="#946f3a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6572" d="M1928.4 412.5c.1 5.7-1.9 11.2 4.1 11.3 5.9.1 6.2-3.1 6.2-5.9 0-2.8-.1-4.7-.1-4.7" class="st26" fill="#e09c5c" stroke="#946f3a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><g id="g6574"><path stroke-miterlimit="10" id="path6576" d="M1930.3 478.8l-54.2 14.5c-6.4 1.7-13-2.1-14.7-8.5l-.3-1c-1.7-6.4 2.1-13 8.5-14.7l54.2-14.5c6.4-1.7 13 2.1 14.7 8.5l.3 1c1.7 6.4-2.2 13-8.5 14.7z" class="st10" fill="#fff" stroke="#7d7d65" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6578" d="M2002.5 559.5l-32.9 8.8c-5.6 1.5-11.4-1.9-12.9-7.4-1.5-5.6 1.9-11.4 7.4-12.9l32.9-8.8c5.6-1.5 11.4 1.9 12.9 7.4 1.6 5.6-1.8 11.4-7.4 12.9z" class="st10" fill="#fff" stroke="#7d7d65" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6580" d="M2037.3 515.3l-10.8 2.9c-5.6 1.5-11.4-1.9-12.9-7.4-1.5-5.6 1.9-11.4 7.4-12.9l10.8-2.9c5.6-1.5 11.4 1.9 12.9 7.4 1.5 5.6-1.8 11.4-7.4 12.9z" class="st10" fill="#fff" stroke="#7d7d65" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></g><path id="path6582" d="M1933.9 372.1l-2.9-.1c-1 0-1.8-.9-1.8-1.9v-.4c0-1 .9-1.8 1.9-1.8l2.9.1c1 0 1.8.9 1.8 1.9v.4c0 1.1-.9 1.9-1.9 1.8z" class="st31" fill="#e68a4c"/><path stroke-miterlimit="10" id="path6584" d="M1986.7 443.9c7.1-1.4 15.8-4.7 20.3-12" class="st36" fill="none" stroke="#51a1b5" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6586" d="M1883.3 432.1c3.7 9.5 9.5 14.2 22.5 16" class="st36" fill="none" stroke="#51a1b5" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><circle id="circle6588" r="2.0999999" cy="424.70001" cx="2009.9" class="st16" fill="#51a1b5"/><path stroke-miterlimit="10" id="path6590" d="M1949 587.1c-27.7 0-52.4-13.1-68.2-33.4" fill="none" stroke="#66a77c" stroke-width="12" stroke-linecap="round" stroke-linejoin="round"/><g id="g6592"><path d="M1934.9 271.4v30.3h-13.5l22.6 27.6 22.6-27.6h-13.4v-30.3z" stroke-miterlimit="10" id="polygon6594" class="st80" fill="#587faa" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M1939.7 275.79999v10.5" stroke-miterlimit="10" id="line6596" class="st76" fill="none" stroke="#fff" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path d="M1939.7 293.39999V301.5" stroke-miterlimit="10" id="line6598" class="st76" fill="none" stroke="#fff" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></g></g><g id="g6600" transform="translate(-1343.3836 -75.800003)"><g id="g6602"><path id="path6604" d="M1784.9 522.9c0-.6-.4-1-1-1h-29.3v-16.3h29.3c.6 0 1-.4 1-1v-11.4l25.1 20.5-25.1 20.5v-11.3z" class="st143" fill="#f1614e"/><path id="path6606" d="M1785.9 495.4l22.5 18.4-22.5 18.4v-9.3c0-1.1-.9-2-2-2h-28.3v-14.3h28.3c1.1 0 2-.9 2-2v-9.2m-2-4.3v13.5h-30.3v18.3h30.3v13.5l27.6-22.6-27.6-22.7z" class="st144" fill="#9c3b2d"/></g><path d="M1757.9 509.10001h10.6" stroke-miterlimit="10" id="line6608" class="st79" fill="#f47c53" stroke="#fdbc61" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path d="M1775.6 509.10001h8" stroke-miterlimit="10" id="line6610" class="st79" fill="#f47c53" stroke="#fdbc61" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></g><g id="g6612" transform="translate(-1343.3836 -75.800003)"><g id="g6614"><path id="path6616" d="M2084.4 419.8l23.3-17.7c.2-.2.3-.4.4-.7 0-.3 0-.5-.2-.7l-6.9-9 32.4 1.1-7.5 31.5-6.9-9c-.2-.3-.5-.4-.8-.4-.2 0-.4.1-.6.2l-23.3 17.7-9.9-13z" class="st143" fill="#f1614e"/><path id="path6618" d="M2103.1 392.7l29 1-6.7 28.3-5.6-7.4c-.3-.4-.8-.7-1.3-.8h-.3c-.4 0-.9.1-1.2.4l-22.5 17.1-8.7-11.4 22.5-17.1c.9-.7 1-1.9.4-2.8l-5.6-7.3m-4.2-2.1l8.2 10.7-24.1 18.3 11.1 14.6 24.1-18.3 8.2 10.7 8.3-34.7-35.8-1.3z" class="st144" fill="#9c3b2d"/></g><path d="M2089.1001 420.5l8.3999-6.29999" stroke-miterlimit="10" id="line6620" class="st79" fill="#f47c53" stroke="#fdbc61" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path d="M2103.2 409.89999L2109.6001 405" stroke-miterlimit="10" id="line6622" class="st79" fill="#f47c53" stroke="#fdbc61" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></g><path stroke-miterlimit="10" id="path6624" d="M47.9164 462.8c13.3-4.1 19.9.8 23.6 7.7 8.2 15.4-1.9 21.1-28.8 31.2-27 10.1-40.4 15.2-40.4 15.2l-1.3-56.9c14.7-5.1 25.3 2.9 27 8.8-.1.1 9.5-3.9 19.9-6" fill="#587faa" stroke="#000" stroke-width="2"/><path id="path6626" d="M2.4164 474.9c2.1-.4 3.9-.6 5.4-.6 4.6 0 8.1 1.5 10 8.6l.5 2 2-.6c.1 0 10.4-3.4 22.4-7.4 2.5-.8 4.9-1.3 7.1-1.3 7.7 0 12.2 5.3 13.3 15.9-5.1 3.1-12.6 6.1-22 9.6l-37.8 14.2-.9-40.4z" class="st82" fill="#2d3f68"/><path stroke-miterlimit="10" id="path6628" d="M6.0164 564.9l12.2 25.7s7.2 5.9 11.4-3.8l-15.6-27-8 5.1z" class="st146" fill="#191b22" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><g id="g6630" transform="translate(-1343.3836 -75.800003)"><path id="path6632" d="M1346.3 567.9c6.1-2.8 11.8-4.2 16.9-4.2 11.8 0 19.1 7.9 19.3 21.1 0 .3.2.6.4.8.2.1.4.2.6.2h.3s2-.6 4.8-.6c5.8 0 13.5 2.3 17 13.4 5.1 15.9-14.6 25.5-29 32.5l-.8.4c-14.1 6.9-20.2 9.4-28.7 13l-.7.3-.1-76.9z" class="st147" fill="#587faa"/><path id="path6634" d="M1363.2 562.6v2c11.4 0 18.1 7.3 18.3 20.1 0 .6.3 1.2.8 1.6.3.3.8.4 1.2.4.2 0 .4 0 .6-.1 0 0 1.9-.5 4.6-.5 5.5 0 12.7 2.2 16.1 12.7 4.9 15.1-14.4 24.4-28.5 31.3l-.8.4c-13.7 6.7-19.8 9.3-27.9 12.7l-.3-74.7c5.7-2.6 11.1-3.9 15.9-3.9v-2m0 0c-5.3 0-11.3 1.5-17.9 4.6l.3 78.9c9.6-4.1 15.5-6.5 30.7-13.9 14.6-7.1 35.8-17.1 30.3-34.1-3.9-12-12.5-14.1-18-14.1-3 0-5.1.6-5.1.6-.3-14-8.2-22-20.3-22z"/></g><path id="path6636" d="M3.9164 509.1c1.1-.1 2.2-.2 3.2-.2 12.9 0 16.5 10.9 17.2 20l.3 3.3 2.8-1.8c.1 0 6.3-4 12.9-4 7.3 0 12.3 4.8 14.9 14.4-6.1 5.5-15.1 9.9-22.4 13.5l-.7.4c-13.7 6.7-19.8 9.3-27.9 12.7l-.3-58.3z" class="st82" fill="#2d3f68"/><g id="g6638" transform="translate(-1343.3836 -75.800003)"><path id="path6640" d="M1346.2 707.5c7-2.8 13.8-4.3 19.8-4.3 13.2 0 21.1 7 21.6 19.3.4 8-9.2 14.2-9.3 14.2-.5.3-.6.9-.3 1.3.2.3.5.5.9.5.2 0 .3 0 .5-.1s16.6-8.5 28.6-14.3c4-1.9 8.2-2.9 12.1-2.9 7.4 0 13.4 3.4 16.5 9.2 4.6 8.8-2.8 14.9-3.1 15.2-.4.3-.5.8-.2 1.3.2.3.5.5.9.5.1 0 .2 0 .3-.1 0 0 3.3-1.2 7.6-1.2 5.6 0 13.3 2 18.1 11.7 8.5 16.9-4.7 24.5-12.6 29-9 5.2-38.1 23.4-39.8 24.4l-61.1 24.7-.5-128.4z" class="st147" fill="#587faa"/><path id="path6642" d="M1366 702.1v2c6 0 11.1 1.6 14.6 4.5 3.8 3.1 5.8 7.8 6.1 13.9.3 7.4-8.8 13.3-8.9 13.4-.9.6-1.2 1.8-.7 2.7.4.6 1 1 1.7 1 .3 0 .6-.1.9-.2.2-.1 16.5-8.5 28.6-14.3 3.8-1.9 7.9-2.8 11.7-2.8 7 0 12.7 3.2 15.6 8.7 4.2 8.1-2.6 13.7-2.8 13.9-.8.6-1 1.7-.5 2.5.4.6 1 1 1.7 1 .2 0 .5 0 .7-.1 0 0 3.1-1.1 7.3-1.1 7.7 0 13.5 3.7 17.2 11.1 7.4 14.8-2.4 22.1-12.2 27.7-8.9 5.1-37.4 22.9-39.7 24.4l-59.7 24.1-.4-126.3c6.6-2.6 13.1-4 18.8-4v-2.1m0 0c-6.3 0-13.5 1.6-20.9 4.7l.4 130.6 62.6-25.3s30.5-19.1 39.8-24.5c7.7-4.4 21.8-12.6 13-30.3-5-10-13-12.2-19-12.2-4.6 0-7.9 1.3-7.9 1.3s8.4-6.7 3.4-16.4c-3.3-6.3-9.8-9.8-17.3-9.8-4 0-8.3 1-12.6 3-12.2 5.9-28.6 14.3-28.6 14.3s10.2-6.5 9.8-15.1c-.6-13.8-9.9-20.3-22.7-20.3z"/></g><path id="path6644" d="M3.8164 646.2c1.5-.3 2.9-.4 4.2-.4 4.5 0 8.1 1.5 10.4 4.2 2.5 2.9 3.5 7.2 2.8 12.2l-.3 2 2 .3s3 .5 4.7 3.1c1.3 2 1.6 4.9.7 8.4l-1 4 3.7-1.6c.2-.1 16.1-7.1 25.7-11.7 2.3-1.1 5.1-2.7 7.5-3.5 4.9-1.7 10.7 1.2 12.5 3.6 2 2.7 5.8 11.9 1.2 16.6l-12.5 9.8 9.3-3.9c.1-.1 13.1-7.5 22.9-7.5 2.3 0 4.1.4 5.4 1.3 6.6 4.1 11.3 10.7 12.7 17.7-3.2 4.5-8.5 7.6-12.1 9.7-8.9 5.1-37.4 22.9-39.7 24.4l-59.7 24.1-.4-112.8z" class="st82" fill="#2d3f68"/><path d="M30.1164 786l13.1 18.6h-24.5z" stroke-miterlimit="10" id="polygon6646" class="st146" fill="#191b22" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><g id="g6648" transform="translate(-1343.3836 -75.800003)"><path id="path6650" d="M1346.3 879.4l-.5-70.5c11.7-7 21.4-10.6 28.7-10.6 7.6 0 12.3 3.9 14 11.5.1.3.3.6.6.7.1 0 .2.1.4.1s.4-.1.6-.2c.1-.1 12.8-9.1 25.8-9.1 7.3 0 13.3 2.9 17.9 8.5 5.5 6.8 7.7 13.4 6.5 19.4-1.9 9.1-10.8 15.5-18 19.3-4.2 2.2-11.1 5.7-19 9.7-14.6 7.4-32.7 16.7-40.6 21.1h-16.4z" class="st147" fill="#587faa"/><path id="path6652" d="M1374.4 799.3c7.1 0 11.5 3.6 13 10.7.1.7.6 1.2 1.2 1.4.2.1.5.1.7.1.4 0 .8-.1 1.2-.4.1-.1 12.5-8.9 25.2-8.9 7 0 12.8 2.7 17.2 8.1 5.3 6.6 7.4 12.8 6.3 18.6-1.9 9.2-11.8 15.6-17.5 18.6-4.2 2.2-11 5.7-18.9 9.7-14.5 7.4-32.4 16.5-40.5 21h-15l-.5-68.9c11.3-6.6 20.6-10 27.6-10m0-2c-7.1 0-16.8 3.3-29.7 11l.5 72h17.5c11.8-6.7 47.4-24.5 59.8-31 12.4-6.5 28.3-20 11.9-40.2-5.4-6.6-12.1-8.9-18.7-8.9-13.5 0-26.4 9.3-26.4 9.3-1.5-7.3-6.2-12.2-14.9-12.2z"/></g><path id="path6654" d="M3.8164 802.6l-.4-55.6c8.8-4 15.1-5.8 19.6-5.8 4.5 0 9.8 1.5 10.9 13.2l.3 2.7 2.5-1c.8-.3 20.3-8.4 28-12.3 1.7-.9 4.9-1.8 8.5-1.8 6.1 0 16.4 2.6 18.2 20-3.8 4.6-9.2 7.9-13.1 10-4.2 2.2-11.1 5.7-19 9.7-14.5 7.4-32.4 16.5-40.5 21h-15z" class="st82" fill="#2d3f68"/><path stroke-miterlimit="10" id="path6656" d="M262.5164 805c120.1-133.6 245.9-175.3 283.3-185.2-11.4-12.4-24.7-17.1-37.5-28.3l-.2-7.2c-142.4 45.1-254 137.7-326 219 0 0-.2.6-.7 1.7h81.1z" class="st146" fill="#191b22" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><g id="g6658" transform="translate(-1343.3836 -75.800003)"><path id="path6660" d="M1537.3 879.8c40.6-45 85.7-85.2 134.2-119.5 59.5-42.1 122.3-73.7 186.7-94.1 10.1 9.2 19.9 18.9 29.1 28.8-38.1 10.4-163.2 52.8-281.9 184.8h-68.1z" class="st82" fill="#2d3f68"/><path id="path6662" d="M1858 667.3c9.6 8.7 18.8 17.8 27.5 27.2-20.7 5.7-59.7 18.8-107.9 45.5-63.1 35-121.2 81.7-172.6 138.8h-65.4c40.1-44.3 84.7-83.9 132.5-117.7 59.3-41.9 121.8-73.4 185.9-93.8m.1-2.1c-140.4 44.4-250.9 135.2-323 215.6h70.8c120.1-133.6 245.9-175.3 283.3-185.2-9.7-10.6-19.9-20.7-30.6-30.4h-.5z"/></g><circle stroke-miterlimit="10" id="circle6664" r="56.5" cy="215.40001" cx="893.21649" class="st1" fill="none" stroke="#95b893" stroke-width="2"/><g id="g6666" transform="translate(-1343.3836 -75.800003)"><path id="path6668" d="M2236.6 202.6c48.8 0 88.6 39.7 88.6 88.6 0 48.9-39.7 88.6-88.6 88.6-48.9 0-88.6-39.8-88.6-88.6s39.7-88.6 88.6-88.6m0-3.3c-50.7 0-91.8 41.1-91.8 91.8 0 50.7 41.1 91.8 91.8 91.8 50.7 0 91.8-41.1 91.8-91.8 0-50.7-41.1-91.8-91.8-91.8z" class="st148" fill="#95b893"/></g><circle stroke-miterlimit="10" id="circle6670" r="46.299999" cy="410" cx="1059.1165" class="st149" fill="none" stroke="#95b893" stroke-width="3.25040007"/><g id="g6672" transform="translate(-1343.3836 -75.800003)"><path id="path6674" d="M2402.5 414.1c39.5 0 71.7 32.2 71.7 71.7s-32.2 71.7-71.7 71.7-71.7-32.2-71.7-71.7 32.1-71.7 71.7-71.7m0-3.3c-41.4 0-75 33.6-75 75s33.6 75 75 75 75-33.6 75-75-33.6-75-75-75z" class="st148" fill="#95b893"/></g><circle stroke-miterlimit="10" id="circle6676" r="51.099998" cy="569.60004" cx="893.21649" class="st149" fill="none" stroke="#95b893" stroke-width="3.25040007"/><circle stroke-miterlimit="10" id="circle6678" r="94.699997" cy="569.60004" cx="893.21649" class="st149" fill="none" stroke="#95b893" stroke-width="3.25040007"/><circle stroke-miterlimit="10" id="circle6680" r="76.099998" cy="569.60004" cx="893.21649" class="st149" fill="none" stroke="#95b893" stroke-width="3.25040007"/><circle stroke-miterlimit="10" id="circle6688" r="11.7" cy="523.5" cx="810.21649" fill="#51a1b5" stroke="#31446e" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><circle stroke-miterlimit="10" id="circle6690" r="11.7" cy="405.90002" cx="985.0163" fill="#a7658d" stroke="#4b2254" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><circle stroke-miterlimit="10" id="circle6692" r="8.1000004" cy="445.29999" cx="1090.6165" fill="#a8c875" stroke="#4b2254" stroke-width="2.75029993" stroke-linecap="round" stroke-linejoin="round"/><circle stroke-miterlimit="10" id="circle6694" r="11.7" cy="617.10004" cx="951.71649" class="st29" fill="#fbe6c6" stroke="#668794" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><circle id="circle6696" r="7" cy="617.40002" cx="956.31635" class="st30" fill="#668794"/><circle stroke-miterlimit="10" id="circle6698" r="19.9" cy="280.80002" cx="829.71649" class="st29" fill="#fbe6c6" stroke="#668794" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><circle id="circle6700" r="11.9" cy="286.10001" cx="823.91644" class="st30" fill="#668794"/><circle stroke-miterlimit="10" id="circle6702" r="14.2" cy="499.70001" cx="958.61639" fill="#a34c4c" stroke="#381916" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><circle id="circle6704" r="2.5999999" cy="500.40002" cx="951.61639" class="st39" fill="#381916"/><circle id="circle6706" r="2.5999999" cy="492.29999" cx="954.61639" class="st39" fill="#381916"/><circle id="circle6708" r="2.5999999" cy="493.60004" cx="962.5163" class="st39" fill="#381916"/><ellipse stroke-miterlimit="10" id="ellipse6710" ry="8.2004271" rx="6.6003442" cy="1128.4603" cx="-116.2824" class="st84" transform="rotate(-73.770504)" fill="#ad6a3b" stroke="#663f09" stroke-width="1.65338624"/><ellipse stroke-miterlimit="10" id="ellipse6712" ry="4.4000001" rx="5.4000001" cy="1138.6377" cx="198.54999" class="st84" transform="rotate(-58.7950001)" fill="#ad6a3b" stroke="#663f09" stroke-width="1.65330005"/><ellipse stroke-miterlimit="10" id="ellipse6714" ry="4.8000002" rx="4.4000001" cy="717.47546" cx="892.77887" class="st84" transform="rotate(-18.4680007)" fill="#ad6a3b" stroke="#663f09" stroke-width="1.65330005"/><circle stroke-miterlimit="10" id="circle6716" r="14.2" cy="234.09999" cx="949.71649" fill="#ce8c59" stroke="#381916" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><circle id="circle6718" r="2.5999999" cy="234.7" cx="942.71649" class="st39" fill="#381916"/><circle id="circle6720" r="2.5999999" cy="226.7" cx="945.71649" class="st39" fill="#381916"/><circle id="circle6722" r="2.5999999" cy="227.99998" cx="953.71649" class="st39" fill="#381916"/><g id="g6724" transform="translate(-1343.3836 -75.800003)"><path stroke-miterlimit="10" id="path6726" d="M1606.2 856.8c19-20 38.1-34.7 38.1-34.7" class="st153" fill="#ce8c59" stroke="#587faa" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6728" d="M1621.3 816.8c21-19.5 42.5-34.2 42.5-34.2" class="st153" fill="#ce8c59" stroke="#587faa" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6730" d="M1675.5 791.4c22.5-19 42-30.8 42-30.8" class="st153" fill="#ce8c59" stroke="#587faa" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6732" d="M1717 743.5c22-15.1 42-23.9 42-23.9" class="st153" fill="#ce8c59" stroke="#587faa" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6734" d="M1768.8 733.2c23.4-14.7 51.3-25.9 51.3-25.9" class="st153" fill="#ce8c59" stroke="#587faa" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6736" d="M1792.7 703.9c23.9-11.7 48.4-20.5 48.4-20.5" class="st153" fill="#ce8c59" stroke="#587faa" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6738" d="M1844 697.6c14.7-6.8 25.4-9.8 25.4-9.8" class="st153" fill="#ce8c59" stroke="#587faa" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path stroke-miterlimit="10" id="path6740" d="M1554.4 879.3c21-23 42-41 42-41" class="st153" fill="#ce8c59" stroke="#587faa" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></g><g id="g6742" transform="translate(-1343.3836 -75.800003)"><g id="g6744"><path d="M2402.5 485.79999l-1.5 1.40002" stroke-miterlimit="10" id="line6746" class="st1" fill="none" stroke="#95b893" stroke-width="2"/><path d="M2398.2 489.89999L2309.7 575" stroke-miterlimit="10" id="line6748" fill="none" stroke="#95b893" stroke-width="2" stroke-dasharray="3.9,3.9"/><path d="M2308.3 576.40002l-1.5 1.39997" stroke-miterlimit="10" id="line6750" class="st1" fill="none" stroke="#95b893" stroke-width="2"/></g></g><g id="g6752" transform="translate(-1343.3836 -75.800003)"><g id="g6754"><path d="M2236.6001 550.70001v-2" stroke-miterlimit="10" id="line6756" class="st1" fill="none" stroke="#95b893" stroke-width="2"/><path d="M2236.6001 544.59998V295.20001" stroke-miterlimit="10" id="line6758" fill="none" stroke="#95b893" stroke-width="2" stroke-dasharray="4.0554,4.0554"/><path d="M2236.6 293.2v-2l1.3 1.5" stroke-miterlimit="10" id="polyline6760" class="st1" fill="none" stroke="#95b893" stroke-width="2"/><path d="M2240.3999 295.70001l111.5 130.79999" stroke-miterlimit="10" id="line6762" fill="none" stroke="#95b893" stroke-width="2" stroke-dasharray="3.9514,3.9514"/><path d="M2353.2 428l1.3 1.5" stroke-miterlimit="10" id="line6764" class="st1" fill="none" stroke="#95b893" stroke-width="2"/></g></g><g id="g6766" transform="translate(-1343.3836 -75.800003)"><circle stroke-miterlimit="10" id="circle6768" r="33.799999" cy="291.20001" cx="2236.6001" class="st83" fill="#ebb548" stroke="#663f09" stroke-width="3.30660009" stroke-linecap="round" stroke-linejoin="round"/><g id="g6770"><circle id="circle6772" r="3" cy="273.60001" cx="2218.8" class="st33" fill="#fff"/></g></g><g id="g6774" transform="translate(-1343.3836 -75.800003)"><circle stroke-miterlimit="10" id="circle6776" r="33.799999" cy="291.20001" cx="2236.6001" class="st83" fill="#ebb548" stroke="#663f09" stroke-width="3.30660009" stroke-linecap="round" stroke-linejoin="round"/><g id="g6778"><circle id="circle6780" r="3" cy="273.60001" cx="2218.8" class="st33" fill="#fff"/></g></g><circle id="circle6906" r="3.9000001" cy="445.29999" cx="1090.6165" fill="#b4dcf5"/></svg> diff --git a/app/javascript/images/screen_hello.svg b/app/javascript/images/screen_hello.svg new file mode 100644 index 000000000..7bcdd0afd --- /dev/null +++ b/app/javascript/images/screen_hello.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="803.09998" width="1197.8285" viewBox="0 0 1197.8284 803.09996" id="Layer_1"><path id="path6218" d="M419.21425 718.72051s1 40 2 51 3 16 20 17 41-2 50-4 16-6 14-29-4-51-4-51" class="st6" fill="#e09c5c" stroke="#946f3a" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6220" d="M504.21425 771.52051l-.1-.6c-1.5-5.7-4.5-7-8-6.3-4.8 1-6.1 7.7-6.9 16.7 0 .4-.1.8-.1 1.3l.7.3c.4-.1.9-.2 1.3-.3 5.9-1.2 10.9-3.4 13.1-11.1z" class="st2" fill="#fbc16c" stroke="#946f3a" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6222" d="M510.81425 399.72051c46-5 79-28 84-32 0 0-14-12-3-20s18 2 18 2 23.5-29.9 43.5-13.9c20 16 27.5 32.9 20.5 48.9s-52 72-147 85" class="st2" fill="#fbc16c" stroke="#946f3a" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6224" d="M239.81425 697.72051c-16 15-41 23-54 5s-4-26-7-30-21 8-13 31 54 48 97 14" class="st4" fill="#e09c5c"/><path id="path6226" d="M500.21425 361.82051s34.6 70.9 43.1 175.4c7.2 89 15 139-46 168s-142 24-180 25-78-4-88-47 .5-76.5.5-76.5 1.4-71.2-2.1-82.7-9.2-28.7-9.2-28.7-5.7 40.1-35.5 53.6c-29.8 13.6-56.1.8-53.9-36.5 0 0-25.5-42.6-37.600004-79.9-12.1-37.3 19.800004-73.9 78.300004-58.8 2-71 63.6-130.7 149.1-137.7 85.4-7.1 106.3 38.7 151.9 40.7 44 2 75-12 98-35s70-53 107-29c0 0 18-6 20 10s-7 16-12 19c0 0 7 22-7 25s-12-18-12-18-23-3-42 27-47.2 70.2-122.6 86.1c-16.1 1.4-29.5-3.9-29.5-3.9" class="st2" fill="#fbc16c" stroke="#946f3a" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6228" d="M259.81425 704.72051c0 23-4 53-5 64s3 22.4 21 23c28 1 82 5 87-12 6.3-21.6 5-58 0-70" class="st6" fill="#e09c5c" stroke="#946f3a" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6230" d="M295.01425 792.42051c.8-9.7 1.8-15.7 6.8-16.7s8.8 3.1 8.9 17.1" class="st2" fill="#fbc16c" stroke="#946f3a" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6232" d="M311.01425 792.42051c.8-9.7 1.8-15.7 6.8-16.7s8.8 3.1 8.9 17.1" class="st2" fill="#fbc16c" stroke="#946f3a" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6234" d="M326.81425 792.72051c.8-9.7 2-17 7-18s8.9 2.1 9 16" class="st2" fill="#fbc16c" stroke="#946f3a" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6236" d="M332.61425 746.52051h-15.4c-7.8 0-14.2-6.4-14.2-14.2 0-7.8 6.4-14.2 14.2-14.2h15.4c7.8 0 14.2 6.4 14.2 14.2 0 7.8-6.4 14.2-14.2 14.2z" fill="#fbc16c"/><path id="path6238" d="M127.01425 433.92051c7.3 9.1 20.6 38.2 19.8 61-.9 22.9 23.8 32.8 36.8 13 13-19.8 16.6-58.4 7.6-77.2-9-18.9-13.9-26-13.9-26s-4.9 9-17.5 10.8c-12.6 1.8-13-3.1-17.3-9.8 0 0 .6 7.4-10.6 11.5-11.2 4.2-14.8-5-14.8-5s2.7 12.7 9.9 21.7z" class="st4" fill="#e09c5c"/><path id="path6240" d="M309.31425 728.32051c-21.3 0-36.8-2.7-48.9-8.4-15.4-7.3-25-19.4-29.1-37.2-9.8-42 .4-75.1.5-75.4l.1-.3v-.3s.1-3.7.1-9.7c13.9.5 33.3 2.8 50.3 10.8 16.8 7.9 27.4 19.6 31.6 34.6 9.3 33.4 33.5 49.6 74.1 49.6 12.6 0 27-1.5 43.9-4.6 8.6-1.6 17.1-2.8 25.3-4 38.7-5.6 72.8-10.5 87.9-47.9-3.3 28.9-14.8 51.7-48.6 67.8-51.3 24.4-116.2 24.5-159.1 24.6-7.7 0-14.3 0-20.1.2-2.9.1-5.5.2-8 .2z" class="st4" fill="#e09c5c"/><path id="path6242" d="M417.81425 390.12051c1.3-8.5 2-14.7-7.8-12.6-9.8 2.1-69.8 18-80.5 20.3-10.8 2.3-8.7 12.1 1.8 18s39.7 23.2 51.7 21.6c12-1.6 22.7-3.8 27.6-19.2 4.9-15.3 7.2-28.1 7.2-28.1z" fill="#2e2314" stroke="#2b2011" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><g transform="translate(-77.185754 15.820514)" id="g6244"><path id="path6246" d="M458.6 419.7c-11.4 0-36.3-14.2-46.9-20.2l-2.2-1.3c-5.1-2.9-8.4-7.1-7.9-10.2.3-2 2.3-3.4 5.5-4.1 5.5-1.2 24-5.9 41.8-10.5l3.8-1c15.9 4.2 28.4 14.9 33.4 28.7l-.2.7c-4.5 14-13.6 16.2-25.9 17.8-.5.1-.9.1-1.4.1z" fill="#ad4949"/></g><g transform="translate(-77.185754 15.820514)" id="g6248"><path id="path6250" d="M404 397c-3.6-2.6-5.6-6.3-5-9.5.5-2.8 2.7-4.7 6.3-5.4 5.8-1.2 25.1-6.2 43.7-11 8.9-2.3 17.5-4.5 24.3-6.2l3.4 15L404 397z" class="st33" fill="#fff"/><path id="path6252" d="M471.9 367.4l2.5 11.1-70 16.4c-2.5-2.1-3.9-4.7-3.5-6.9.5-2.6 3.2-3.5 4.8-3.9 5.8-1.2 25.1-6.2 43.8-11 8.2-2.1 16-4.1 22.4-5.7m3-4.9c-20.4 5.1-61.2 15.8-70 17.7-10.3 2.2-10.6 13-1.3 19l75.6-17.8-4.3-18.9z" class="st42" fill="#7d7d65"/></g><path id="path6254" d="M215.61425 319.92051c-20.7 3.4-36.9-.3-43.7-27.4-6.8-27.1 17-58.9 51.2-71.3 34.2-12.4 68-2.3 68-2.3s9.5-22.5 12.8-35.5c3.3-13 13.3-14.1 23.6 5.2 0 0 28.8-4.1 32.3 26.8 3.6 30.9-20.7 46.4-20.7 46.4" class="st2" fill="#fbc16c" stroke="#946f3a" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6256" d="M327.61425 188.52051c4.3 10.3 2.9 22.9 2.7 27" class="st2" fill="#fbc16c" stroke="#946f3a" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><g transform="translate(-77.185754 15.820514)" id="g6258"><path id="path6260" d="M358.6 376.9c2.2 5.7 6.2 9.9 8.9 8.3 2.7-1.6 2.9-5.7.4-12.4-2.4-6.6-13.6-29.8-16.6-34.3-3-4.5-6.4-5.8-9.1-4.2-2.7 1.6-1.9 5.5-.1 9.2 1.9 3.8 16.5 33.4 16.5 33.4z"/></g><g transform="translate(-77.185754 15.820514)" id="g6262"><path id="path6264" d="M387.3 470.6c-4.3 0-8.8-.5-13.2-1.6-21.2-5.2-32.9-21.5-36.5-34-2.3-8-1.6-14.9 2-18.5 3.2-3.3 6.7-5 10.2-5 7.5 0 14.4 7.2 17.8 11.5 7.6 9.5 14.8 14.3 21.4 14.3 5.3 0 9.9-3 13.7-9 10-15.4 12.7-34.1 8.2-55.4-1.5-7.1.3-11.2 2.1-13.4 1.8-2.3 4.5-3.6 7.3-3.6 1.6 0 3 .4 4.2 1.2 18.2 11.8 20.2 52 13.6 74.4-5.6 18.8-23.9 39.1-50.8 39.1z" class="st33" fill="#fff"/><path id="path6266" d="M420.2 357.9c1.2 0 2.2.3 3.1.9 7.6 4.9 13 16 15.2 31.2 2 13.6 1 29.3-2.5 41-2.9 9.9-9.1 19.2-17.3 26-9.1 7.6-20 11.6-31.5 11.6-4.2 0-8.4-.5-12.7-1.6-20.4-5-31.6-20.7-35-32.6-2.1-7.2-1.5-13.5 1.5-16.5 2.9-2.9 5.8-4.4 8.8-4.4 6.9 0 13.7 7.5 16.3 10.7 8 10 15.7 15 22.9 15 6 0 11.2-3.3 15.4-9.9 10.3-15.9 13.1-35 8.4-56.9-1.1-4.9-.5-9.1 1.7-11.7 1.5-1.8 3.6-2.8 5.7-2.8m0-4c-6.7 0-14.1 6.4-11.3 19.4 3.6 16.6 3.6 36-7.9 53.8-3.8 5.9-8 8.1-12.1 8.1-8.2 0-16.1-8.8-19.8-13.5-3.8-4.8-11.1-12.2-19.4-12.2-3.8 0-7.7 1.5-11.7 5.6-11 11.3 2 47.6 35.5 55.8 4.7 1.2 9.3 1.7 13.7 1.7 26.7 0 46.6-19.8 52.7-40.5 7.1-24 4.6-64.4-14.4-76.7-1.5-1-3.4-1.5-5.3-1.5z" class="st42" fill="#7d7d65"/></g><path id="path6268" d="M290.11425 457.32051c6.5 1.7 19.4 2 29.8-6.6" fill="none" stroke="#7d7d65" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6270" d="M173.31425 408.82051c-3.7 5-10.1 7.9-16.3 7.2-6.2-.6-11.9-4.7-14.5-10.4-1.1 5.4-5.3 10.2-10.6 11.5-5.3 1.3-11.4-.7-14.8-5 1.6 13 12 22.7 16.5 35" fill="none" stroke="#946f3a" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6272" d="M263.31425 420.02051l-17.1 6.5c-5 1.9-10.6-.6-12.4-5.6-1.9-5 .6-10.6 5.6-12.4l17.1-6.5c5-1.9 10.6.6 12.4 5.6 1.9 4.9-.6 10.5-5.6 12.4z" fill="#e68a4c"/><path id="path6274" d="M625.31425 218.52051c7.3-2.8 14.1-4.3 19.2-3.8" class="st34" fill="none" stroke="#fbd7a3" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6276" d="M561.31425 267.62051c11.7-11.4 21.9-20.7 31.6-29.6 3.9-3.5 8.7-7.1 13.9-10.3" class="st34" fill="none" stroke="#fbd7a3" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6278" d="M471.21425 293.12051c15.7 1.4 33.6-.1 52.7-6.6" class="st34" fill="none" stroke="#fbd7a3" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6280" d="M361.61425 250.82051c19.9 3.5 36.4 12.2 56.1 23.6" class="st34" fill="none" stroke="#fbd7a3" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><g transform="translate(-77.185754 15.820514)" id="g6282"><path id="path6284" d="M311 486.9c-48 22-68 37-57 78s59 41 59 41-8 25 18 28 55-5 56-41-20-61-63-65" class="st2" fill="#fbc16c" stroke="#946f3a" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6286" d="M313 605.9c10 2 25 2 25 2" class="st2" fill="#fbc16c" stroke="#946f3a" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6288" d="M354.2 548.6l6.5 6.8c4.9 5.1 4.7 13.3-.4 18.2l-1.9 1.8c-5.1 4.9-13.3 4.7-18.2-.4l-6.5-6.8c-4.9-5.1-4.7-13.3.4-18.2l1.9-1.8c5.1-4.9 13.3-4.7 18.2.4z" fill="#fdd06e"/></g><path id="path6290" d="M396.81425 426.32051l-6.5 2.6c-4.9 1.9-10.5-.5-12.4-5.4l-.7-1.8c-1.9-4.9.5-10.5 5.4-12.4l6.5-2.6c4.9-1.9 10.5.5 12.4 5.4l.7 1.8c2 4.9-.5 10.5-5.4 12.4z" fill="#b9655a"/><path id="path6292" d="M218.51425 495.22051c1.8-14 .6-22.1.6-22.1" class="st2" fill="#fbc16c" stroke="#946f3a" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/></svg> diff --git a/app/javascript/images/screen_interactions.svg b/app/javascript/images/screen_interactions.svg new file mode 100644 index 000000000..41873371a --- /dev/null +++ b/app/javascript/images/screen_interactions.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="803" width="1195.8" viewBox="0 0 1195.8 802.99999" id="Layer_1"><path id="path6294" d="M132.1 466.49998c-6 5.8-11.8 5-18.2-.6-2.5-2.2-4.6.9-.6 4.9 4 4 13.5 8.3 22 .8" class="st99" fill="#e09c5c" stroke="#946f3a" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6296" d="M295.2 263.49998h-59.8c-15 0-27.3 12.3-27.3 27.3v12.9c0 6.3 2.2 12.1 5.8 16.8-6.7 13.7-21.4 16.9-21.4 16.9 18.9 2.3 24.6-3.5 30.7-9.2 3.7 1.8 7.8 2.9 12.2 2.9h59.8c15 0 27.3-12.3 27.3-27.3v-12.9c.1-15.1-12.2-27.4-27.3-27.4z" fill="#fff" stroke="#668794" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6298" d="M176.3 472.49998c-.1 11.9-2.4 21.1 7.9 21.2 10.3.1 13.2-2.5 13.1-10.1-.1-7.6-.6-21-.6-21" class="st99" fill="#e09c5c" stroke="#946f3a" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6300" d="M161.8 365.79998c-20.2 0-35.1 7.6-35.1 36v54.3c0 8 4.4 21.8 21.8 21.8h24.1c8.5 0 16.3-2.8 22.5-9.4 6.2-6.6 9.2-13.3 9.2-25.5v-46.6s20.4-.5 29.4-13.3c8.9-12.8 6.9-28.2 5.7-32.6 0 0 5.1-.5 4-4.4-1.1-3.9-7.3-3.3-10.1-1.1 0 0-1.6-1.8-7-1.7-5.4.1-3.7 6.2.7 6.7 0 0 2.4 13.8-4.6 18.7-7 4.9-21.4 6.6-29.8 2.7-8.5-3.7-11.3-5.6-30.8-5.6z" class="st101" fill="#fbc16c" stroke="#946f3a" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6302" d="M148.5 476.79998c-20.1 0-20.6-18.5-20.6-20.6v-23.1c13 .1 18.8 1.5 18.8 12.4 0 13.2 2.2 19.8 13.9 19.8H192c1.7 0 3.4-.1 5-.7-.8 1-1.7 2.1-2.7 3.2-5.6 6-12.9 9-21.6 9h-24.2z" class="st4" fill="#e09c5c"/><path id="path6304" d="M154.8 372.09998c-9.6-2.9-14.7-4.9-11.8-13.3 2.9-8.4 17-6.6 24.7-2.7 0 0 4-2.4 6.3-3.3 2.3-.9 6.6-1.3 6.7 1.7 0 0 5.4.6 4.4 4.9 0 0 6.4 3.7-8.1 10.7" class="st101" fill="#fbc16c" stroke="#946f3a" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6306" d="M138.5 381.49998c0-6.4-3.8-10.3-11.8-10.3s-14.8 7.5-13 18.7c1.8 11.2-4.3 19.9 2.8 24.9s14.4 1.9 16.7-1.9c0 0 7.6 2 7-9.9" class="st101" fill="#fbc16c" stroke="#946f3a" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6308" d="M167.3 395.79998c-5.7 0-8.8 4.4-7.5 10.2 1.3 5.7 5 10 11.4 10.2 6.4.1 12.5-2.1 14.4-11.4 1.9-9.3-1.6-9.4-4.4-9.4-2.8 0-13.9.4-13.9.4z" fill="#544024" stroke="#3b3024" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6310" d="M176.8 408.89998c0-6.7-5.4-12.2-12.2-12.2-.6 0-1.1.1-1.7.1-2.8 1.6-4.1 5-3.2 9.1 1.3 5.7 5 10 11.4 10.2 1.1 0 2.2 0 3.3-.2 1.6-2 2.4-4.4 2.4-7z" fill="#693131" stroke="#381916" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6312" d="M181.1 402.49998v-7.2c-3 0-13.8.4-13.8.4-4.5 0-7.4 2.8-7.8 6.8h21.6z" class="st104" fill="#fff" stroke="#7d7d65" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6314" d="M146.2 410.69998c6 5.8 14.3 5.8 18.1 2.6 3.8-3.2 5.8-11.4 2.7-12.2-3.1-.8-2.4 4.7-5.2 6.3-2.8 1.6-6 1.2-9-.7-2.9-1.9-5.9-3.2-7.4-1.4-1.3 2-.6 4 .8 5.4z" class="st104" fill="#fff" stroke="#7d7d65" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6316" d="M349.4 440.69998c-5.7 6.2-14 6.6-18 3.6-4-3-6.4-11-3.4-12 3-1 2.7 4.5 5.5 6s6.1.9 8.9-1.1c2.8-2 5.7-3.6 7.3-1.7 1.6 1.9 1.1 3.8-.3 5.2z" fill="none" stroke="#000" stroke-width=".57069999" stroke-miterlimit="10"/><path id="path6318" d="M134.3 415.29998l-20.5.1s-.7-3.4-3.8-2.9c-3.1.5-3.8 2.3-3.9 3.4 0 0-6.8.4-6.9 7.2-.1 6.8 4.9 9.5 12.1 9.1 7.2-.4 24.4 0 24.4 0" class="st101" fill="#fbc16c" stroke="#946f3a" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6320" d="M135.4 472.49998c-.1 11.9-2.4 21.1 7.9 21.2 10.3.1 13.2-2.5 13.1-10.1-.1-7.6-.2-10.4-.2-10.4" class="st99" fill="#e09c5c" stroke="#946f3a" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6322" d="M152.7 399.79998c-.9 0-1.6-.7-1.6-1.6v-10.9c0-.9.7-1.6 1.6-1.6.9 0 1.6.7 1.6 1.6v10.9c0 .9-.7 1.6-1.6 1.6z" fill="#402f19"/><path id="path6324" d="M524.8 296.99998c-6.9-8.5-22.1-16.7-42.9-6.4-20.8 10.3-26.2 38-14.9 56.5 11.3 18.5 14.4 22.6 14.4 22.6v46.7c0 9.8 2.8 13.3 10 13.3s11.3-2.8 11.3-9.8v-10.5h29.5v11c0 4.3 2.6 9 9.5 9s9.8-2.3 9.8-11.8v-62.6s.8-27.4-26.7-58z" class="st106" fill="#fbc16c" stroke="#946f3a" stroke-width="3.56489992" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6326" d="M491.4 427.89998c-6.2 0-8.2-2.8-8.2-11.6v-47.3l-.4-.5s-2-2.7-8.5-13.1c.1-1.4.2-2.8.1-4.1l13 17.2v22c0 5.2 1.1 11.2 9.9 11.2h52.3v15.8c0 9-2.6 10-8 10-7.2 0-7.7-5.5-7.7-7.2v-12.7H501v12.3c0 5.5-3 8-9.6 8z" class="st4" fill="#e09c5c"/><path id="rect6328" stroke="#3b3024" stroke-width="3.56489992" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M510.59998 170.29993h206.2v203.5h-206.2z"/><g transform="translate(-733 -934.40002)" id="g6330"><path id="path6332" d="M1216.8 1264.5c-.8 0-1.6-.4-2-1.1l-5.3-9c-.3-.5-.4-1.2-.2-1.7.2-.6.5-1.1 1.1-1.4 1.1-.6 2.5-.3 3.2.8l5.3 9c.6 1.1.3 2.5-.8 3.2-.5.1-.9.2-1.3.2z" fill="#141a1c"/></g><path id="path6334" d="M294.8 452.99998c-7.2.9-16.7-.7-21.6-1.8-4.9-1.1-7.8 4.3-8.5 9.5-.7 5.2 11.7 14.1 32.7 8.7" class="st108" fill="#fbe6c6" stroke="#668794" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6336" d="M363 450.19998c13.1 4.1 22.2 11.7 22 20.3-.2 8.6-9.6 14.2-16.5 15.5-6.9 1.3-9.8-6.1-8.9-11.1.9-5 3.4-6.6 3.4-6.6" class="st108" fill="#fbe6c6" stroke="#668794" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6338" d="M358 465.59998c5 2.6 10.1 5.4 10.1 5.4" class="st108" fill="#fbe6c6" stroke="#668794" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6340" d="M364 411.99998c0-6.4 3.8-10.3 11.8-10.3s14.8 7.5 13 18.7c-1.8 11.2 4.3 19.9-2.8 24.9-7.1 4.9-14.4 1.9-16.7-1.9 0 0-7.6 2-7-9.9" class="st108" fill="#fbe6c6" stroke="#668794" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6342" d="M328.1 395.29998c-29.7-.3-34.4 7.9-42 14.4-7.6 6.6-17.6 18.8-36.6 19.5 0 0-2.5-3.5-5.2-1s-2.2 5.9-.3 7.2c0 0-3.7 3.7.2 7.6 3.9 3.9 7.2 0 7.2 0s14.4 1.3 25.9-4c11.4-5.4 15.1-3.3 15.1-3.3v50.4c0 15.8 7.2 22 25.9 22s36.8 1.7 44.5-6.2c7.7-7.9 6.4-19.2 5.9-29.2s0-28.1 0-43.5c0-15.4-1.4-33.6-40.6-33.9z" class="st108" fill="#fbe6c6" stroke="#668794" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6344" d="M340.3 505.59998c0 4.8-.5 6.6-.2 10.1-1.6 1.1-2.2 3-1.7 4.6.6 2 3.6 4 9.9 3.9 10.4.1 13.2-2.5 13.1-10.1-.1-3.5-.2-8.2-.3-12.2" class="st109" fill="#dcceb5" stroke="#668794" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6346" d="M334.5 506.99998c-2.6 0-5.1 0-7.9-.1-2.8 0-5.6-.1-8.4-.1-18 0-24.7-5.7-24.7-20.9 0-2.5 4.3-4.8 9-4.8 2.3 0 6.6.6 9.2 4.3 3.8 5.2 8.9 7.1 16.3 7.1 4.5 0 14.5.2 17.9-2.4 4.5-3.3 5.8-7.9 13.3-9.6 3-.7 5.6-1.2 8.6-.4.2 9.3-1.3 17.2-5.9 20.8-5.5 4.4-14.5 6.1-27.4 6.1z" fill="#dcceb5"/><path id="path6348" d="M341.9 402.79998c7-.1 10.7-4.4 10.2-8.6-.6-4.1-3.5-9.9-16.6-9.2-5.8.3-8.7 4.1-11.5 4.1-2.8-.1-8.3-8.1-13.1-7.6-4.8.5-5.1 3.9-4.3 5.5 0 0-4.3 1.3-1.9 6.9 2.4 5.6 7.3 7.5 12.1 8.1" class="st108" fill="#fbe6c6" stroke="#668794" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6350" d="M301.7 503.09998c0 4.8-.5 9.2-.2 12.7-1.6 1.1-2.2 3-1.7 4.6.6 2 3.6 4 9.9 3.9 10.4.1 13.2-2.5 13.1-10.1 0-2.9-.1-3.7-.2-7.2" class="st109" fill="#dcceb5" stroke="#668794" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6352" d="M469.1 323.09998c-3.8-5.7-8.6-7.6-16.5-3.7-7.9 3.9-8.7 14.8-4.6 21 4.2 6.3 4.8 11.2 3.8 15.8-1 4.6.5 7.1 3.7 8 3.2.9 5.2-1.1 5.2-1.1s11.2 1.1 12.4-8c1.2-9.1-.2-12.9-.2-12.9" class="st106" fill="#fbc16c" stroke="#946f3a" stroke-width="3.56489992" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6354" d="M460.7 363.09998c2-2.5 2-7.6 2-7.6" fill="none" stroke="#946f3a" stroke-width="3.56489992" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6356" d="M468.8 306.69998c-7.3-3.2-9.2-12.8-2.8-17 6.4-4.2 13.3-4.3 13.3-4.3s7.1-12.2 11.7-12.6c4.6-.4 6 2.7 5.2 5.2 0 0 5.9-1.1 6 3.8.1 5-2.6 7.8-6.4 10.6" class="st106" fill="#fbc16c" stroke="#946f3a" stroke-width="3.56489992" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6358" d="M492.8 340.79998c7.3 2.2 15.1 2.2 19.6-4.9 4.5-7.1 6.9-14.3 2.9-15.8s-5.4 2-7.2 6.5c-1.8 4.5-5.1 6-10.8 5.1-5.7-.9-7.7 0-8.1 2.7-.4 2.7.3 5.3 3.6 6.4z" fill="#fff" stroke="#4a493c" stroke-width="3.56489992" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6360" d="M633.8 211.49998c.7-8.6 12.3-17.4 29.3-11.5 17 5.9 30.2 19.1 26.7 44.7 0 0 13.1 53.9 13.6 80l-31.7 1.6 1.1-57.3c0 .1-50.3-41.3-39-57.5z" fill="#fbdeb5" stroke="#946f3a" stroke-width="3.56489992" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6362" d="M672.8 269.09998c-.4-14.1.7-22.5.7-22.5" class="st113" fill="#fbc16c" stroke="#946f3a" stroke-width="1.78240001" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6364" d="M634.7 208.59998c6.4-7.9 18.4-6.8 28.2-1.2 9.9 5.5 15.4 12 15.4 12" fill="none" stroke="#946f3a" stroke-width="1.78240001" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6366" d="M628.4 215.39998c-2.1-4.9 9.9-8.8 18.2-3.6 8.3 5.2 18.6 15.2 18.5 26.7-.1 11.5-11.2 9.1-11.2 9.1s-25.1-31.4-25.5-32.2z" class="st115" fill="#fbe6c6" stroke="#668794" stroke-width="3.56489992" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6368" d="M613 293.99998c4.3 13.7 10.6 20.3 10.6 20.3l31.8-16.4-7.9-12.1-34.5 8.2z" class="st106" fill="#fbc16c" stroke="#946f3a" stroke-width="3.56489992" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6370" d="M663.1 258.19998c0 25.5-21.2 42.3-45.8 44-28.8 2-43.4-18.5-43.4-44s20-46.1 44.6-46.1 44.6 20.6 44.6 46.1z" class="st45" fill="#fbc16c"/><path id="path6372" d="M625.4 301.09998c-2.6.6-5.3.9-8.1 1.1-28.8 2-43.4-18.5-43.4-44s20-46.1 44.6-46.1 44.6 20.7 44.6 46.1c0 7.1-1.6 13.5-4.5 19.1" class="st106" fill="#fbc16c" stroke="#946f3a" stroke-width="3.56489992" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6374" d="M587.5 247.69998c-18.4 13.4-32.3 13.5-41.8 4.6 0 0 1.6-5.8-4.2-4.8-5.8 1-7.8 7.2-5.4 10.4 0 0-2.6 4.2-.4 8.4s8 2.8 8 2.8 8.4 9.4 20.3 9.4 21.1-3.4 25.9-8.4" class="st106" fill="#fbc16c" stroke="#946f3a" stroke-width="3.56489992" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6376" d="M605.2 282.59998c-.3-2.5-1.6-4.8-3.6-6.3s-4.6-2.2-7-1.8c-.9.1-1.9.5-2.3 1.3-1.3-.4-2.6-.7-3.9-.5-1.3.2-2.6.8-3.3 2-.7 1.1-.7 2.7.2 3.7-.4 2.4.3 5 2.3 6.4.8.5 1.7.9 2.6 1.1 2.1.6 4.4.7 6.5 0 1.3-.4 2.4-1.1 3.5-1.8 1.7-1.2 3.7-2.3 5-4.1z" fill="#a34c4c" stroke="#47271c" stroke-width="1.78240001" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6378" d="M598.6 281.49998c-2.1 2.2-5.4 3.2-8.4 2.7-1.7-.3-3.5-1.3-4-3 1.8-1.4 4-2.1 6.3-2.1 2.2 0 4.3 1 6.1 2.4z" fill="#fbe6c6" stroke="#47271c" stroke-width="1.78240001" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><circle id="circle6380" r="7.1999998" cy="271.90002" cx="619.09998" fill="#a34c4c"/><path id="path6382" d="M600.6 250.59998c2.1 4.5 3.4 5.7 6.3 6.6 2.9.9 7.5-.6 9.4-3.2 0 0-2.2-3.7-7.2-4.9-5-1.1-8.5 1.5-8.5 1.5z" class="st118" fill="#fbe6c6" stroke="#946f3a" stroke-width="1.78240001" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6384" d="M586.5 247.19998s.9-4.5-8.9-7.4c-1.2 2.8-2.1 5.8-2.7 8.9 6.5 1.3 9.4.8 11.6-1.5z" class="st118" fill="#fbe6c6" stroke="#946f3a" stroke-width="1.78240001" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6386" d="M648.7 250.59998c-5 13.3-5.7 28.4-1.9 33.9 3.9 5.6 9.1 4.2 9.9 1.1 0 0 .7 5.1 6.6 5.7 5.8.6 10.3-6.8 10.2-19-.1-12.2-3.7-21-12.9-21.9-9.2-.9-11.9.2-11.9.2z" class="st106" fill="#fbc16c" stroke="#946f3a" stroke-width="3.56489992" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6388" d="M580.6 233.89998c12.3-2.5 26.9-2.1 42.9 7.1s30.1 14 39.6 14.9c-1.1-24.5-20.6-44-44.5-44-16.1.1-30.2 8.9-38 22z" fill="#51a1b5" stroke="#31446e" stroke-width="3.56489992" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6390" d="M602.6 240.79998c1.7-1 3.7-1.5 5.6-1.6" class="st113" fill="#fbc16c" stroke="#946f3a" stroke-width="1.78240001" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6392" d="M611.7 238.89998c1 0 1.9.2 2.8.5" class="st113" fill="#fbc16c" stroke="#946f3a" stroke-width="1.78240001" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6394" d="M582.4 235.49998c1.8.3 3.5 1.3 4.8 2.6" class="st113" fill="#fbc16c" stroke="#946f3a" stroke-width="1.78240001" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6396" d="M583.6 242.49998c-.3.2-.6.4-.9.7-1.5 1.7-1.4 4.3.2 5.9 1.5-.3 2.6-1 3.6-2 0 .1.5-2.2-2.9-4.6z" class="st120" fill="#4d3b21" stroke="#2b2111" stroke-width="1.78240001" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6398" d="M616.3 253.99998s-1.6-2.8-5.3-4.3c-.4.2-.9.5-1.2.9-1.6 1.8-1.4 4.5.3 6.1.2.2.5.4.7.5 2.2-.4 4.4-1.6 5.5-3.2z" class="st120" fill="#4d3b21" stroke="#2b2111" stroke-width="1.78240001" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6400" d="M689.8 244.69998c-2.3-7.7-5.4-11.6-5.4-11.6" class="st106" fill="#fbc16c" stroke="#946f3a" stroke-width="3.56489992" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6402" d="M617.3 323.09998c-3.1 9.1-5.8 21.9 2 32.1l-2.2 11.4 80.7 2.3s-1-32.5-15.5-42.8c-14.4-10.4-15.9-11.5-18.9-15-3.1-3.5-5.9-9.7-5.9-9.7l-40.2 21.7z" class="st121" fill="#876a4d" stroke="#3b3024" stroke-width="3.56489992" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6404" d="M655.1 296.49998c-18 2.9-31.5 11.9-38.2 22.5-3.8 6-3.2 10.1-3.2 10.1l47.2-28-5.8-4.6z" class="st121" fill="#876a4d" stroke="#3b3024" stroke-width="3.56489992" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6406" d="M611.7 365.49998c.5-12.8 3.5-34.8 20.8-36.8s39.3 3.1 55.7 38.2l-76.5-1.4z" class="st121" fill="#876a4d" stroke="#3b3024" stroke-width="3.56489992" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6408" d="M503.8 163.59998v218.9h218.9v-218.9H503.8zm201.9 201.9H520.8v-184.9h184.9v184.9z" fill="#594726" stroke="#45331b" stroke-width="3.56489992" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="rect6410" fill="none" stroke="#45331b" stroke-width="2.67370009" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M510.59998 170.29993h206.2v206.2h-206.2z"/><path id="path6412" d="M657.5 312.19998c6.5.1 10.6 4.6 10.6 4.6" fill="none" stroke="#3b3024" stroke-width="1.78240001" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6414" d="M685.6 325.79998c-2.9-26.5-7.3-51.7-7.3-51.7" class="st113" fill="#fbc16c" stroke="#946f3a" stroke-width="1.78240001" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="line6416" class="st113" fill="#fbc16c" stroke="#946f3a" stroke-width="1.78240001" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M689.30005 325.29993l-.6001-27.79993"/><circle id="circle6418" r="7.6999998" cy="298" cx="655.40002" class="st115" fill="#fbe6c6" stroke="#668794" stroke-width="3.56489992" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6420" d="M613.4 236.29998c3.3 1.2 6.6 2.8 10 4.7 16 9.2 30.1 14 39.6 14.9-.7-15.2-8.5-28.5-20.1-36.4-11.9 1.5-22.4 7.8-29.5 16.8z" fill="#31446e" stroke="#31446e" stroke-width="3.56489992" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6422" d="M501.1 346.99998c5.7 4.6 14.7 8.5 20.1 6.6 7.7-2.7 12.7-4.6 15.8 2.2 3.1 6.8-1.9 10.8-10.1 15.2-9.9 5.2-18.6.3-26.3-3.7" class="st106" fill="#fbc16c" stroke="#946f3a" stroke-width="3.56489992" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6424" d="M576.7 380.89998c0 4.9-3.9 7-8.8 7s-8.8-2.2-8.8-7 3.9-7.7 8.8-7.7 8.8 2.9 8.8 7.7z" class="st106" fill="#fbc16c" stroke="#946f3a" stroke-width="3.56489992" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6426" d="M599.5 225.49998c8.4.5 16.9 1.8 28.6 7.8" fill="none" stroke="#31446e" stroke-width="2.67370009" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="line6428" class="st127" fill="none" stroke="#fbf0d3" stroke-width="2.67370009" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M604.59998 175.5h3.59997"/><path id="line6430" class="st127" fill="none" stroke="#fbf0d3" stroke-width="2.67370009" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M622.30005 175.5h64"/><path id="line6432" class="st127" fill="none" stroke="#fbf0d3" stroke-width="2.67370009" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M594.69995 175.5h5.6001"/><path id="line6434" class="st127" fill="none" stroke="#fbf0d3" stroke-width="2.67370009" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M519.30005 175.5H585"/><g transform="translate(-733 -934.40002)" id="g6436"><path id="path6438" d="M1025.8 1218.9h-54.6c-1.9 0-3.4-1.5-3.4-3.4s1.5-3.4 3.4-3.4h54.6c1.9 0 3.4 1.5 3.4 3.4s-1.5 3.4-3.4 3.4z" class="st72" fill="#4a4439"/><path id="path6440" d="M1025.8 1235.8h-54.6c-1.9 0-3.4-1.5-3.4-3.4s1.5-3.4 3.4-3.4h54.6c1.9 0 3.4 1.5 3.4 3.4 0 1.8-1.5 3.4-3.4 3.4z" class="st72" fill="#4a4439"/><path id="path6442" d="M1025.8 1252.6h-54.6c-1.9 0-3.4-1.5-3.4-3.4s1.5-3.4 3.4-3.4h54.6c1.9 0 3.4 1.5 3.4 3.4s-1.5 3.4-3.4 3.4z" class="st72" fill="#4a4439"/></g><path id="path6444" d="M303.9 411.99998c0-6.4 3.8-10.3 11.8-10.3s14.8 7.5 13 18.7c-1.8 11.2 4.3 19.9-2.8 24.9-7.1 4.9-14.4 1.9-16.7-1.9 0 0-7.6 2-7-9.9" class="st108" fill="#fbe6c6" stroke="#668794" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="line6446" class="st128" fill="#fbe6c6" stroke="#28353b" stroke-width="7.12979984" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M540.80005 202.29993h45.09997"/><path id="line6448" class="st128" fill="#fbe6c6" stroke="#28353b" stroke-width="7.12979984" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M540.80005 313.40002h45.09997"/><path id="line6450" class="st128" fill="#fbe6c6" stroke="#28353b" stroke-width="7.12979984" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M556.09998 330.69995h29.80004"/><path id="line6452" class="st128" fill="#fbe6c6" stroke="#28353b" stroke-width="7.12979984" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M540.80005 348.40002h45.09997"/><path id="line6454" class="st128" fill="#fbe6c6" stroke="#28353b" stroke-width="7.12979984" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M540.80005 218.40002h11.79993"/><path id="path6456" d="M202.3 427.59998c11.6-.6 21.6-4.6 26.7-9.3 5.8-5.3 8.9-10.5 3.7-15.1-5.1-4.5-10.1-.1-10.1-.1s-2-3.1-5-.8-.8 4.5-.8 4.5-3.6 5-14.9 5.5" class="st101" fill="#fbc16c" stroke="#946f3a" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6458" d="M147 400.59998l-3.5.6c-1 .2-2-.5-2.1-1.5-.2-1 .5-2 1.5-2.1l3.5-.6c1-.2 2 .5 2.1 1.5.2 1-.4 2-1.5 2.1z" class="st31" fill="#e68a4c"/><path id="path6460" d="M482.8 337.99998l-5.4.9c-1.6.3-3.1-.8-3.3-2.4-.3-1.6.8-3.1 2.4-3.3l5.4-.9c1.6-.3 3.1.8 3.3 2.4.3 1.6-.8 3.1-2.4 3.3z" class="st31" fill="#e68a4c"/><path id="path6462" d="M217.7 375.79998c.7-.2 1-.4 1.6-.7" class="st129" fill="none" stroke="#fbd7a3" stroke-width="2.85330009" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6464" d="M212.5 377.19998c.9-.2.4 0 1.2-.2" class="st129" fill="none" stroke="#fbd7a3" stroke-width="2.85330009" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6466" d="M200.2 377.39998c2.2.3 4.4.4 6.6.4" class="st129" fill="none" stroke="#fbd7a3" stroke-width="2.85330009" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6468" d="M186.1 373.39998c2.4 1.1 5 2 7.7 2.7" class="st129" fill="none" stroke="#fbd7a3" stroke-width="2.85330009" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6470" d="M943 464.89998c-8.7 8.4-17 7.2-26.3-.8-3.6-3.1-6.6 1.3-.8 7.1s19.4 12 31.7 1.2" class="st130" fill="#e09c5c" stroke="#946f3a" stroke-width="3.29229999" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6472" d="M1006.7 473.49998c-.2 17.1-3.5 30.4 11.5 30.6 15 .2 19.1-3.7 18.9-14.6-.2-11-.8-30.2-.8-30.2" class="st130" fill="#e09c5c" stroke="#946f3a" stroke-width="3.29229999" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6474" d="M985.8 319.59998c-29.1 0-50.6 10.9-50.6 51.9v78.4c0 11.6 6.3 31.4 31.4 31.4h34.7c12.2 0 23.5-4 32.4-13.6 8.9-9.6 13.2-19.2 13.2-36.7v-67.1s29.4-.7 42.3-19.2c12.9-18.5 9.9-40.7 8.3-47 0 0 7.4-.7 5.8-6.3-1.6-5.6-10.5-4.7-14.6-1.6 0 0-2.3-2.6-10.1-2.4-7.8.2-5.3 9 1 9.6 0 0 3.5 20-6.6 27s-30.9 9.5-43 3.9c-12.1-5.5-16.1-8.3-44.2-8.3z" class="st131" fill="#fbc16c" stroke="#946f3a" stroke-width="3.29229999" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6476" d="M983.3 370.49998c8.6 5.9 19.2 7.4 26.8.7" class="st132" fill="none" stroke="#402f19" stroke-width="4.32690001" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6478" d="M966.6 479.69998c-28.9 0-29.8-26.7-29.8-29.8v-33.3c18.8.2 27.1 2.2 27.1 17.9 0 19 3.1 28.6 20.1 28.6h45.2c2.5 0 4.9-.1 7.2-1.1-1.1 1.5-2.4 3-3.9 4.6-8.1 8.7-18.6 13-31.2 13h-34.7z" class="st4" fill="#e09c5c"/><path id="path6480" d="M975.8 328.69998c-13.8-4.1-21.2-7-17.1-19.1 4.1-12.1 24.5-9.5 35.6-3.9 0 0 5.8-3.4 9.1-4.7 3.3-1.3 9.5-1.9 9.7 2.4 0 0 7.8.8 6.4 7 0 0 9.3 5.3-11.7 15.4" class="st131" fill="#fbc16c" stroke="#946f3a" stroke-width="3.29229999" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6482" d="M952.2 342.29998c0-9.3-5.4-14.8-17-14.8s-21.4 10.8-18.7 27c2.7 16.2-6.2 28.7 4 35.9 10.2 7.2 20.8 2.7 24.1-2.7 0 0 11 2.9 10-14.3" class="st131" fill="#fbc16c" stroke="#946f3a" stroke-width="3.29229999" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6484" d="M963.3 384.39998c8.7 8.4 20.6 8.4 26.1 3.8 5.5-4.6 8.4-16.4 4-17.5-4.4-1.2-3.5 6.7-7.5 9.1-4 2.3-8.7 1.7-12.9-1-4.2-2.7-8.5-4.7-10.6-2-2.1 2.7-1.1 5.6.9 7.6z" fill="#fff" stroke="#7d7d65" stroke-width="3.29229999" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6486" d="M947.8 473.49998c-.2 17.1-3.5 30.4 11.5 30.6 15 .2 19.1-3.7 18.9-14.6-.2-11-.3-15-.3-15" class="st130" fill="#e09c5c" stroke="#946f3a" stroke-width="3.29229999" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6488" d="M965.4 369.79998l-6.7 1.1c-1 .2-2-.5-2.1-1.5l-.3-1.6c-.2-1 .5-2 1.5-2.1l6.7-1.1c1-.2 2 .5 2.1 1.5l.3 1.6c.2.9-.5 1.9-1.5 2.1z" class="st31" fill="#e68a4c"/><path id="path6490" d="M1066.5 333.99998c1-.4 1.4-.5 2.4-1" class="st134" fill="none" stroke="#fbd7a3" stroke-width="4.11530018" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6492" d="M1059 336.09998c1.2-.2.5 0 1.7-.3" class="st134" fill="none" stroke="#fbd7a3" stroke-width="4.11530018" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6494" d="M1041.1 336.39998c3.1.4 6.3.6 9.5.5" class="st134" fill="none" stroke="#fbd7a3" stroke-width="4.11530018" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6496" d="M1020.8 330.59998c3.4 1.5 7.2 2.9 11.1 3.9" class="st134" fill="none" stroke="#fbd7a3" stroke-width="4.11530018" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6498" d="M305.1 402.39998c.7-.3 1.3-.5 1.9-.8" class="st135" fill="none" stroke="#fff" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6500" d="M299.6 405.09998c.9-.5.9-.6 1.8-1.1" class="st135" fill="none" stroke="#fff" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6502" d="M291.8 410.49998c1.6-1.2 2.5-1.9 4.1-2.9" class="st135" fill="none" stroke="#fff" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6504" d="M284.1 417.79998c1.2-1.3 2.6-2.6 4-3.9" class="st135" fill="none" stroke="#fff" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6506" d="M525.2 357.69998c.9-.3 1.8-.7 2.7-1.1" class="st136" fill="none" stroke="#fbd7a3" stroke-width="4.45609999" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6508" d="M514.6 358.79998c1.5.2 2.9.3 4.5.1" class="st136" fill="none" stroke="#fbd7a3" stroke-width="4.45609999" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6510" d="M501 353.69998c2.7 1.4 5.2 2.7 7.7 3.6" class="st136" fill="none" stroke="#fbd7a3" stroke-width="4.45609999" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="line6512" class="st137" fill="none" stroke="#bf9649" stroke-width="3.56489992" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M711.69995 298.90002v3"/><path id="line6514" class="st137" fill="none" stroke="#bf9649" stroke-width="3.56489992" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M711.69995 290.90002V293.5"/><path id="line6516" class="st137" fill="none" stroke="#bf9649" stroke-width="3.56489992" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M711.69995 243.69995v38.40003"/><g transform="translate(-733 -934.40002)" id="g6518"><path id="polygon6520" class="st33" fill="#fff" d="M1439.3 1141.2l11.7 19.2 19.7 11.1-19.3 11.8-11.1 19.6-11.8-19.2-19.6-11.1 19.3-11.8z"/></g><g transform="translate(-733 -934.40002)" id="g6522"><path id="polygon6524" class="st33" fill="#fff" d="M1426.9 1110.4l8.4-4.9 5-8.4 4.9 8.4 8.4 5-8.4 4.9-5 8.4-4.9-8.4z"/></g><path id="line6526" class="st137" fill="none" stroke="#bf9649" stroke-width="3.56489992" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M515.19995 246.59998v-2.90003"/><path id="line6528" class="st137" fill="none" stroke="#bf9649" stroke-width="3.56489992" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M515.19995 254.69995V252"/><path id="line6530" class="st137" fill="none" stroke="#bf9649" stroke-width="3.56489992" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M515.19995 301.90002V263.5"/><g transform="translate(-733 -934.40002)" id="g6782"><path id="rect6784" fill="#587faa" d="M1193.6 1367.5h255.3v95.599998h-255.3z"/><path id="path6786" d="M1447.8 1368.5v93.6h-253.3v-93.6h253.3m2-2h-257.3v97.6h257.3v-97.6z"/></g><path id="rect6788" class="st82" transform="rotate(90)" fill="#2d3f68" d="M446.05344-714.81085h9.1000004v253.2H446.05344z"/><path id="line6790" class="st80" fill="#587faa" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M461.30005 446.09998h255.5"/><path id="line6792" class="st80" fill="#587faa" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M468.30005 463.40002v2.19996"/><path id="line6794" class="st80" fill="#587faa" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M468.30005 446.09998v13.09997"/><path id="line6796" class="st80" fill="#587faa" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M474.40002 463.40002v2.19996"/><path id="line6798" class="st80" fill="#587faa" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M474.40002 446.09998v13.09997"/><path id="line6800" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M464.90002 435.69995h246.79993"/><g transform="translate(-733 -934.40002)" id="g6802"><g id="g6804"><path id="path6806" d="M1453.6 1417.6c0-6.8-2.8-11.9-6.9-11.9-4.1 0-6.7 4.1-6.7 11.9v17.4c0 4.8.5 11.1 6.8 11.1s4.8-17.9 4.8-17.9" class="st158" fill="#f4a679" stroke="#668794" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6808" d="M1506.3 1417.6c0-6.8 2.8-11.9 6.9-11.9 4 0 6.7 4.1 6.7 11.9v17.4c0 4.8-.5 11.1-6.8 11.1s-4.8-17.9-4.8-17.9" class="st158" fill="#f4a679" stroke="#668794" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6810" d="M1540.4 1435.3c-2.4-2.8-6.8-3.1-9.7-.8-6.2 4.9-9.9 7.4-16.1 11v-11.1c0-.6-.1-1.1-.1-1.7.1-.9.1-1.9.1-2.9 0-18.7-15.2-30.7-33.9-30.7s-33.9 12-33.9 30.7c0 1 0 1.9.1 2.9-.1.5-.1 1.1-.1 1.7v9.7c-5.8-3.4-9.4-5.8-15.3-10.6-2.9-2.3-7.2-2-9.7.8-2.4 2.8-1.9 7.3 1.3 9.9 7.6 6 11.7 8.7 20.1 13.4 1.2.7 2.4 1 3.6 1v25.6c0 .4 0 .9.1 1.3 0 .3-.1.6-.1.9v22.6c0 3.2 2.6 5.8 5.8 5.8h7.8c3.2 0 5.8-2.6 5.8-5.8v-12.8h29.3v12.8c0 3.2 2.6 5.8 5.8 5.8h7.8c3.2 0 5.8-2.6 5.8-5.8v-22.6c0-.3 0-.6-.1-.9 0-.4.1-.9.1-1.3v-24.8c1.5.2 3-.1 4.4-.9 8.4-4.7 12.5-7.4 20.1-13.4 2.9-2.4 3.4-6.9 1-9.8z" class="st158" fill="#f4a679" stroke="#668794" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="line6812" class="st108" fill="#fbe6c6" stroke="#668794" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M1461.6 1496.3h40.7"/><path id="path6814" d="M1476.3 1491.8c3.9 9.1 16.6 9.5 23.8 3.7 2.8-2.2 2.4-4.5-1.9-3.7-4.3.7-10.6 3.9-15.6-2" class="st158" fill="#f4a679" stroke="#668794" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/></g><path id="path6816" d="M1463.6 1405.7c-1.8-7.5 1.7-14.4 13.4-12.2 2.7.5 4.8 1.5 4.8 1.5s6-1 11.2-2.8c5.2-1.8 9 .5 9.9 5.4.8 4.9-1.5 8.3-8.9 9.7" class="st158" fill="#f4a679" stroke="#668794" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/></g><g transform="translate(-733 -934.40002)" id="g6818"><path id="path6820" d="M1364.2 1419.2c0-6.8-2.8-11.9-6.9-11.9-4 0-6.7 4.1-6.7 11.9v17.4c0 4.8.5 11.1 6.8 11.1s4.8-17.9 4.8-17.9" class="st159" fill="#965e8c" stroke="#5c3556" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6822" d="M1416.9 1419.2c0-6.8 2.8-11.9 6.9-11.9 4.1 0 6.7 4.1 6.7 11.9v17.4c0 4.8-.5 11.1-6.8 11.1s-4.8-17.9-4.8-17.9" class="st159" fill="#965e8c" stroke="#5c3556" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6824" d="M1426.5 1447.8l-1.3 1.6v-13.3c0-.6-.1-1.1-.1-1.7.1-.9.1-1.9.1-2.9 0-18.7-15.2-33.9-33.9-33.9s-33.9 15.2-33.9 33.9c0 1 0 1.9.1 2.9-.1.5-.1 1.1-.1 1.7v11.7c-13.2 3.2-16 17.8 0 24.1v14c0 .4 0 .9.1 1.3 0 .3-.1.6-.1.9v22.6c0 3.2 2.6 5.8 5.8 5.8h7.8c3.2 0 5.8-2.6 5.8-5.8V1498h29.3v12.8c0 3.2 2.6 5.8 5.8 5.8h7.8c3.2 0 5.8-2.6 5.8-5.8v-22.6c0-.3 0-.6-.1-.9 0-.4.1-.9.1-1.3v-13.5c.4-.1.9-.3 1.3-.5 15.8-6.3 13-21-.3-24.2z" class="st159" fill="#965e8c" stroke="#5c3556" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="line6826" fill="#fbe6c6" stroke="#5c3556" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M1371.8 1498h40.7"/><g id="g6828"><path id="path6830" d="M1416.3 1413.5c.8 1.1 1.5 2.2 2.1 3.4" class="st161" fill="none" stroke="#b895a6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6832" d="M1410.8 1407.6c.9.8 1.8 1.6 2.7 2.5" class="st161" fill="none" stroke="#b895a6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6834" d="M1372 1407.4c5.3-4.2 12-6.7 19.2-6.7 6.4 0 12.3 2 17.3 5.3" class="st161" fill="none" stroke="#b895a6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/></g><path id="polyline6836" class="st162" fill="none" stroke="#5c3556" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M1357.3 1471.9v-10.4l3-2.1"/><path id="polyline6838" class="st162" fill="none" stroke="#5c3556" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M1425.2 1472.4v-11.6l-3-2.1"/></g><path id="path6840" d="M542.7 483.19998c0-6.8-2.8-11.9-6.9-11.9-4.1 0-6.7 4.1-6.7 11.9v17.4c0 4.8.5 11.1 6.8 11.1s4.8-17.9 4.8-17.9" class="st163" fill="#fbe6c6"/><path id="path6842" d="M595.4 483.19998c0-6.8 2.8-11.9 6.9-11.9 4 0 6.7 4.1 6.7 11.9v17.4c0 4.8-.5 11.1-6.8 11.1s-4.8-17.9-4.8-17.9" class="st163" fill="#fbe6c6"/><path id="path6844" d="M629.5 500.89998c-2.4-2.8-6.8-3.1-9.7-.8-6.2 4.9-9.9 7.4-16.1 11v-11.1c0-.6-.1-1.1-.1-1.7.1-.9.1-1.9.1-2.9 0-18.7-15.2-30.7-33.9-30.7s-33.9 12-33.9 30.7c0 1 0 1.9.1 2.9-.1.5-.1 1.1-.1 1.7v9.7c-5.8-3.4-9.4-5.8-15.3-10.6-2.9-2.3-7.2-2-9.7.8-2.4 2.8-1.9 7.3 1.3 9.9 7.6 6 11.7 8.7 20.1 13.4 1.2.7 2.4 1 3.6 1v25.6c0 .4 0 .9.1 1.3 0 .3-.1.6-.1.9v22.6c0 3.2 2.6 5.8 5.8 5.8h7.8c3.2 0 5.8-2.6 5.8-5.8v-12.8h29.3v12.8c0 3.2 2.6 5.8 5.8 5.8h7.8c3.2 0 5.8-2.6 5.8-5.8v-22.6c0-.3 0-.6-.1-.9 0-.4.1-.9.1-1.3v-24.8c1.5.2 3-.1 4.4-.9 8.4-4.7 12.5-7.4 20.1-13.4 2.9-2.4 3.4-6.9 1-9.8z" class="st108" fill="#fbe6c6" stroke="#668794" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="line6846" class="st108" fill="#fbe6c6" stroke="#668794" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M550.30005 561.90002H591"/><path id="path6848" d="M565.4 557.39998c3.9 9.1 16.6 9.5 23.8 3.7 2.8-2.2 2.4-4.5-1.9-3.7-4.3.7-10.6 3.9-15.6-2" class="st108" fill="#fbe6c6" stroke="#668794" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6850" d="M650.8 557.39998c3.9 9.1 16.6 9.5 23.8 3.7 2.8-2.2 2.4-4.5-1.9-3.7-4.3.8-10.6 3.9-15.6-2" class="st159" fill="#965e8c" stroke="#5c3556" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6852" d="M550.5 472.49998c-3.4-7.4 3.2-16.2 17.7-12.8 0 0 16.5-3.7 19.8-2.6 2.7 1 3.2 3.5 0 4.8 0 0 3.2 3.7-.8 5.3 0 0 1.1 4.3-3.6 5.3s-13.4 0-13.4 0" class="st108" fill="#fbe6c6" stroke="#668794" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6854" d="M580.1 466.59998c3.2-.1 7.1.6 7.1.6" class="st108" fill="#fbe6c6" stroke="#668794" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6856" d="M578.6 461.89998s4.8-1 9.3 0" class="st108" fill="#fbe6c6" stroke="#668794" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6858" d="M966.2 358.39998c3.2-4.1 7.7-4.8 10.8 0" class="st132" fill="none" stroke="#402f19" stroke-width="4.32690001" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><g transform="translate(-733 -934.40002)" id="g6860"><path id="polygon6862" class="st164" fill="#e14741" stroke="#552814" stroke-width="3.29229999" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M1819.8 1370.2v-67.5l-38.8-14.3-60.6 14.4v69.2l40.2 17.1z"/><path id="polygon6864" class="st164" fill="#e14741" stroke="#552814" stroke-width="3.29229999" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M1781 1288.4l-60.6 14.4 40.1 13 59.3-13.1z"/><path id="polygon6866" class="st164" fill="#e14741" stroke="#552814" stroke-width="3.29229999" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M1819.8 1302.7l-59.3 13.1.1 73.3 59.2-15.1z"/></g><path id="path6868" d="M950.1 415.39998c13 12.2 29.5 18.9 48.8 15.1 7.6-1.5 9.4-19.1-3.3-19.4 0 0-1.8-6.5-6.1-5.7-4.3.8-2.4 5.7-2.4 5.7s-18.6-4.6-25.9-12.2" class="st131" fill="#fbc16c" stroke="#946f3a" stroke-width="3.29229999" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6874" d="M360.1 496.99998c6 5.8 11.8 5 18.2-.6 2.5-2.2 4.6.9.6 4.9-4 4-13.5 8.3-22 .8" class="st109" fill="#dcceb5" stroke="#668794" stroke-width="2.28270006" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path id="path6876" d="M733.9 463.19998c2.2-1.9 6.1-3.7 14.1 0 0 0 8.5-1.3 13.5-3.3" fill="none" stroke="#f7ceb2" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/><path transform="translate(-733 -934.40002)" id="polygon6878" class="st45" fill="#fbc16c" d="M1784.4 1350.6l-9.2-5.6 12.1-4 4.7-12.3 4.7 10.1 12.1-2.1-9.2 10.2 2.8 11-10.4-3.7-10.4 8.8z"/><circle id="circle6880" r="38.400002" cy="585.90002" cx="616.30005" class="st82" fill="#2d3f68"/><circle id="circle6882" r="38.400002" cy="582.19995" cx="996.80005" class="st82" fill="#2d3f68"/><g transform="translate(-733 -934.40002)" id="g6884"><g id="g6886"><path id="polygon6888" class="st33" fill="#fff" d="M1340.7 1520v7.9h12.5l4.5 6.1h-22.8v-14h-6.5l9.2-10.9 9.4 10.9z"/></g><g id="g6890"><path id="polygon6892" class="st33" fill="#fff" d="M1359.5 1522v-7.9H1347l-4.5-6.1h22.8v13.9l6.5.1-9.3 10.9-9.3-10.9z"/></g></g><circle id="circle6894" r="38.400002" cy="582.19995" cx="265.5" class="st82" fill="#2d3f68"/><path id="path6896" d="M283.1 579.99998c-4.8-8.9-19.9-8.5-25.1-8.1v-7.9l-13.5 13.5 13.5 13.5v-7.6c16.4 0 22.9.4 21.9 16.7 0 .2 9-9.3 3.2-20.1z" class="st33" fill="#fff"/><g transform="translate(-733 -934.40002)" id="g6898"><path id="polygon6900" class="st33" fill="#fff" d="M1738.4 1521.3l3.2 13.4-11.8-7.2-11.8 7.2 3.2-13.4-10.4-9 13.7-1.1 5.3-12.8 5.3 12.8 13.8 1.1z"/></g><path id="line6910" fill="none" stroke="#e69985" stroke-width="3.29229999" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M1032 385l51-12"/></svg> diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js index d24f39ad2..4c145febc 100644 --- a/app/javascript/mastodon/actions/notifications.js +++ b/app/javascript/mastodon/actions/notifications.js @@ -8,6 +8,7 @@ import { importFetchedStatuses, } from './importer'; import { defineMessages } from 'react-intl'; +import { List as ImmutableList } from 'immutable'; import { unescapeHTML } from '../utils/html'; import { getFilters, regexFromFilters } from '../selectors'; @@ -18,6 +19,8 @@ export const NOTIFICATIONS_EXPAND_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST'; export const NOTIFICATIONS_EXPAND_SUCCESS = 'NOTIFICATIONS_EXPAND_SUCCESS'; export const NOTIFICATIONS_EXPAND_FAIL = 'NOTIFICATIONS_EXPAND_FAIL'; +export const NOTIFICATIONS_FILTER_SET = 'NOTIFICATIONS_FILTER_SET'; + export const NOTIFICATIONS_CLEAR = 'NOTIFICATIONS_CLEAR'; export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP'; @@ -88,10 +91,16 @@ export function updateNotifications(notification, intlMessages, intlLocale) { const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS(); +const excludeTypesFromFilter = filter => { + const allTypes = ImmutableList(['follow', 'favourite', 'reblog', 'mention']); + return allTypes.filterNot(item => item === filter).toJS(); +}; + const noOp = () => {}; export function expandNotifications({ maxId } = {}, done = noOp) { return (dispatch, getState) => { + const activeFilter = getState().getIn(['settings', 'notifications', 'quickFilter', 'active']); const notifications = getState().get('notifications'); const isLoadingMore = !!maxId; @@ -102,7 +111,9 @@ export function expandNotifications({ maxId } = {}, done = noOp) { const params = { max_id: maxId, - exclude_types: excludeTypesFromSettings(getState()), + exclude_types: activeFilter === 'all' + ? excludeTypesFromSettings(getState()) + : excludeTypesFromFilter(activeFilter), }; if (!maxId && notifications.get('items').size > 0) { @@ -167,3 +178,14 @@ export function scrollTopNotifications(top) { top, }; }; + +export function setFilter (filterType) { + return dispatch => { + dispatch({ + type: NOTIFICATIONS_FILTER_SET, + path: ['notifications', 'quickFilter', 'active'], + value: filterType, + }); + dispatch(expandNotifications()); + }; +}; diff --git a/app/javascript/mastodon/actions/onboarding.js b/app/javascript/mastodon/actions/onboarding.js index a161c50ef..a1dd3a731 100644 --- a/app/javascript/mastodon/actions/onboarding.js +++ b/app/javascript/mastodon/actions/onboarding.js @@ -1,14 +1,8 @@ -import { openModal } from './modal'; import { changeSetting, saveSettings } from './settings'; -export function showOnboardingOnce() { - return (dispatch, getState) => { - const alreadySeen = getState().getIn(['settings', 'onboarded']); +export const INTRODUCTION_VERSION = 20181216044202; - if (!alreadySeen) { - dispatch(openModal('ONBOARDING')); - dispatch(changeSetting(['onboarded'], true)); - dispatch(saveSettings()); - } - }; +export const closeOnboarding = () => dispatch => { + dispatch(changeSetting(['introductionVersion'], INTRODUCTION_VERSION)); + dispatch(saveSettings()); }; diff --git a/app/javascript/mastodon/components/column_header.js b/app/javascript/mastodon/components/column_header.js index 457508d13..f68e4155e 100644 --- a/app/javascript/mastodon/components/column_header.js +++ b/app/javascript/mastodon/components/column_header.js @@ -37,6 +37,14 @@ class ColumnHeader extends React.PureComponent { animating: false, }; + historyBack = () => { + if (window.history && window.history.length === 1) { + this.context.router.history.push('/'); + } else { + this.context.router.history.goBack(); + } + } + handleToggleClick = (e) => { e.stopPropagation(); this.setState({ collapsed: !this.state.collapsed, animating: true }); @@ -55,16 +63,22 @@ class ColumnHeader extends React.PureComponent { } handleBackClick = () => { - if (window.history && window.history.length === 1) this.context.router.history.push('/'); - else this.context.router.history.goBack(); + this.historyBack(); } handleTransitionEnd = () => { this.setState({ animating: false }); } + handlePin = () => { + if (!this.props.pinned) { + this.historyBack(); + } + this.props.onPin(); + } + render () { - const { title, icon, active, children, pinned, onPin, multiColumn, extraButton, showBackButton, intl: { formatMessage } } = this.props; + const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage } } = this.props; const { collapsed, animating } = this.state; const wrapperClassName = classNames('column-header__wrapper', { @@ -95,7 +109,7 @@ class ColumnHeader extends React.PureComponent { } if (multiColumn && pinned) { - pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={onPin}><i className='fa fa fa-times' /> <FormattedMessage id='column_header.unpin' defaultMessage='Unpin' /></button>; + pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><i className='fa fa fa-times' /> <FormattedMessage id='column_header.unpin' defaultMessage='Unpin' /></button>; moveButtons = ( <div key='move-buttons' className='column-header__setting-arrows'> @@ -104,7 +118,7 @@ class ColumnHeader extends React.PureComponent { </div> ); } else if (multiColumn) { - pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={onPin}><i className='fa fa fa-plus' /> <FormattedMessage id='column_header.pin' defaultMessage='Pin' /></button>; + pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><i className='fa fa fa-plus' /> <FormattedMessage id='column_header.pin' defaultMessage='Pin' /></button>; } if (!pinned && (multiColumn || showBackButton)) { diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js index b2b0265aa..2912540a0 100644 --- a/app/javascript/mastodon/containers/mastodon.js +++ b/app/javascript/mastodon/containers/mastodon.js @@ -1,11 +1,12 @@ import React from 'react'; -import { Provider } from 'react-redux'; +import { Provider, connect } from 'react-redux'; import PropTypes from 'prop-types'; import configureStore from '../store/configureStore'; -import { showOnboardingOnce } from '../actions/onboarding'; +import { INTRODUCTION_VERSION } from '../actions/onboarding'; import { BrowserRouter, Route } from 'react-router-dom'; import { ScrollContext } from 'react-router-scroll-4'; import UI from '../features/ui'; +import Introduction from '../features/introduction'; import { fetchCustomEmojis } from '../actions/custom_emojis'; import { hydrateStore } from '../actions/store'; import { connectUserStream } from '../actions/streaming'; @@ -18,11 +19,39 @@ addLocaleData(localeData); export const store = configureStore(); const hydrateAction = hydrateStore(initialState); -store.dispatch(hydrateAction); -// load custom emojis +store.dispatch(hydrateAction); store.dispatch(fetchCustomEmojis()); +const mapStateToProps = state => ({ + showIntroduction: state.getIn(['settings', 'introductionVersion'], 0) < INTRODUCTION_VERSION, +}); + +@connect(mapStateToProps) +class MastodonMount extends React.PureComponent { + + static propTypes = { + showIntroduction: PropTypes.bool, + }; + + render () { + const { showIntroduction } = this.props; + + if (showIntroduction) { + return <Introduction />; + } + + return ( + <BrowserRouter basename='/web'> + <ScrollContext> + <Route path='/' component={UI} /> + </ScrollContext> + </BrowserRouter> + ); + } + +} + export default class Mastodon extends React.PureComponent { static propTypes = { @@ -31,14 +60,6 @@ export default class Mastodon extends React.PureComponent { componentDidMount() { this.disconnect = store.dispatch(connectUserStream()); - - // Desktop notifications - // Ask after 1 minute - if (typeof window.Notification !== 'undefined' && Notification.permission === 'default') { - window.setTimeout(() => Notification.requestPermission(), 60 * 1000); - } - - store.dispatch(showOnboardingOnce()); } componentWillUnmount () { @@ -54,11 +75,7 @@ export default class Mastodon extends React.PureComponent { return ( <IntlProvider locale={locale} messages={messages}> <Provider store={store}> - <BrowserRouter basename='/web'> - <ScrollContext> - <Route path='/' component={UI} /> - </ScrollContext> - </BrowserRouter> + <MastodonMount /> </Provider> </IntlProvider> ); diff --git a/app/javascript/mastodon/features/account_gallery/index.js b/app/javascript/mastodon/features/account_gallery/index.js index 0d66868ed..96051818b 100644 --- a/app/javascript/mastodon/features/account_gallery/index.js +++ b/app/javascript/mastodon/features/account_gallery/index.js @@ -103,7 +103,7 @@ class AccountGallery extends ImmutablePureComponent { ); } - if (hasMore) { + if (hasMore && !(isLoading && medias.size === 0)) { loadOlder = <LoadMore visible={!isLoading} onClick={this.handleLoadOlder} />; } diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js index 4b56c7fdd..d6add9b0d 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.js +++ b/app/javascript/mastodon/features/compose/components/compose_form.js @@ -47,6 +47,7 @@ class ComposeForm extends ImmutablePureComponent { caretPosition: PropTypes.number, preselectDate: PropTypes.instanceOf(Date), is_submitting: PropTypes.bool, + is_changing_upload: PropTypes.bool, is_uploading: PropTypes.bool, onChange: PropTypes.func.isRequired, onSubmit: PropTypes.func.isRequired, @@ -82,10 +83,10 @@ class ComposeForm extends ImmutablePureComponent { } // Submit disabled: - const { is_submitting, is_uploading, anyMedia } = this.props; + const { is_submitting, is_changing_upload, is_uploading, anyMedia } = this.props; const fulltext = [this.props.spoiler_text, countableText(this.props.text)].join(''); - if (is_submitting || is_uploading || length(fulltext) > maxChars || (fulltext.length !== 0 && fulltext.trim().length === 0 && !anyMedia)) { + if (is_submitting || is_uploading || is_changing_upload || length(fulltext) > maxChars || (fulltext.length !== 0 && fulltext.trim().length === 0 && !anyMedia)) { return; } @@ -161,7 +162,7 @@ class ComposeForm extends ImmutablePureComponent { const { intl, onPaste, showSearch, anyMedia } = this.props; const disabled = this.props.is_submitting; const text = [this.props.spoiler_text, countableText(this.props.text)].join(''); - const disabledButton = disabled || this.props.is_uploading || length(text) > maxChars || (text.length !== 0 && text.trim().length === 0 && !anyMedia); + const disabledButton = disabled || this.props.is_uploading || this.props.is_changing_upload || length(text) > maxChars || (text.length !== 0 && text.trim().length === 0 && !anyMedia); let publishText = ''; if (this.props.privacy === 'private' || this.props.privacy === 'direct') { diff --git a/app/javascript/mastodon/features/compose/containers/compose_form_container.js b/app/javascript/mastodon/features/compose/containers/compose_form_container.js index 5d7fb8852..b4a1c4b44 100644 --- a/app/javascript/mastodon/features/compose/containers/compose_form_container.js +++ b/app/javascript/mastodon/features/compose/containers/compose_form_container.js @@ -22,6 +22,7 @@ const mapStateToProps = state => ({ caretPosition: state.getIn(['compose', 'caretPosition']), preselectDate: state.getIn(['compose', 'preselectDate']), is_submitting: state.getIn(['compose', 'is_submitting']), + is_changing_upload: state.getIn(['compose', 'is_changing_upload']), is_uploading: state.getIn(['compose', 'is_uploading']), showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']), anyMedia: state.getIn(['compose', 'media_attachments']).size > 0, diff --git a/app/javascript/mastodon/features/introduction/index.js b/app/javascript/mastodon/features/introduction/index.js new file mode 100644 index 000000000..6e0617f72 --- /dev/null +++ b/app/javascript/mastodon/features/introduction/index.js @@ -0,0 +1,196 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ReactSwipeableViews from 'react-swipeable-views'; +import classNames from 'classnames'; +import { connect } from 'react-redux'; +import { FormattedMessage } from 'react-intl'; +import { closeOnboarding } from '../../actions/onboarding'; +import screenHello from '../../../images/screen_hello.svg'; +import screenFederation from '../../../images/screen_federation.svg'; +import screenInteractions from '../../../images/screen_interactions.svg'; +import logoTransparent from '../../../images/logo_transparent.svg'; + +const FrameWelcome = ({ domain, onNext }) => ( + <div className='introduction__frame'> + <div className='introduction__illustration' style={{ background: `url(${logoTransparent}) no-repeat center center / auto 80%` }}> + <img src={screenHello} alt='' /> + </div> + + <div className='introduction__text introduction__text--centered'> + <h3><FormattedMessage id='introduction.welcome.headline' defaultMessage='First steps' /></h3> + <p><FormattedMessage id='introduction.welcome.text' defaultMessage="Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name." values={{ domain: <code>{domain}</code> }} /></p> + </div> + + <div className='introduction__action'> + <button className='button' onClick={onNext}><FormattedMessage id='introduction.welcome.action' defaultMessage="Let's go!" /></button> + </div> + </div> +); + +FrameWelcome.propTypes = { + domain: PropTypes.string.isRequired, + onNext: PropTypes.func.isRequired, +}; + +const FrameFederation = ({ onNext }) => ( + <div className='introduction__frame'> + <div className='introduction__illustration'> + <img src={screenFederation} alt='' /> + </div> + + <div className='introduction__text introduction__text--columnized'> + <div> + <h3><FormattedMessage id='introduction.federation.home.headline' defaultMessage='Home' /></h3> + <p><FormattedMessage id='introduction.federation.home.text' defaultMessage='Posts from people you follow will appear in your home feed. You can follow anyone on any server!' /></p> + </div> + + <div> + <h3><FormattedMessage id='introduction.federation.local.headline' defaultMessage='Local' /></h3> + <p><FormattedMessage id='introduction.federation.local.text' defaultMessage='Public posts from people on the same server as you will appear in the local timeline.' /></p> + </div> + + <div> + <h3><FormattedMessage id='introduction.federation.federated.headline' defaultMessage='Federated' /></h3> + <p><FormattedMessage id='introduction.federation.federated.text' defaultMessage='Public posts from other servers of the fediverse will appear in the federated timeline.' /></p> + </div> + </div> + + <div className='introduction__action'> + <button className='button' onClick={onNext}><FormattedMessage id='introduction.federation.action' defaultMessage='Next' /></button> + </div> + </div> +); + +FrameFederation.propTypes = { + onNext: PropTypes.func.isRequired, +}; + +const FrameInteractions = ({ onNext }) => ( + <div className='introduction__frame'> + <div className='introduction__illustration'> + <img src={screenInteractions} alt='' /> + </div> + + <div className='introduction__text introduction__text--columnized'> + <div> + <h3><FormattedMessage id='introduction.interactions.reply.headline' defaultMessage='Reply' /></h3> + <p><FormattedMessage id='introduction.interactions.reply.text' defaultMessage="You can reply to other people's and your own toots, which will chain them together in a conversation." /></p> + </div> + + <div> + <h3><FormattedMessage id='introduction.interactions.reblog.headline' defaultMessage='Boost' /></h3> + <p><FormattedMessage id='introduction.interactions.reblog.text' defaultMessage="You can share other people's toots with your followers by boosting them." /></p> + </div> + + <div> + <h3><FormattedMessage id='introduction.interactions.favourite.headline' defaultMessage='Favourite' /></h3> + <p><FormattedMessage id='introduction.interactions.favourite.text' defaultMessage='You can save a toot for later, and let the author know that you liked it, by favouriting it.' /></p> + </div> + </div> + + <div className='introduction__action'> + <button className='button' onClick={onNext}><FormattedMessage id='introduction.interactions.action' defaultMessage='Finish tutorial!' /></button> + </div> + </div> +); + +FrameInteractions.propTypes = { + onNext: PropTypes.func.isRequired, +}; + +@connect(state => ({ domain: state.getIn(['meta', 'domain']) })) +export default class Introduction extends React.PureComponent { + + static propTypes = { + domain: PropTypes.string.isRequired, + dispatch: PropTypes.func.isRequired, + }; + + state = { + currentIndex: 0, + }; + + componentWillMount () { + this.pages = [ + <FrameWelcome domain={this.props.domain} onNext={this.handleNext} />, + <FrameFederation onNext={this.handleNext} />, + <FrameInteractions onNext={this.handleFinish} />, + ]; + } + + componentDidMount() { + window.addEventListener('keyup', this.handleKeyUp); + } + + componentWillUnmount() { + window.addEventListener('keyup', this.handleKeyUp); + } + + handleDot = (e) => { + const i = Number(e.currentTarget.getAttribute('data-index')); + e.preventDefault(); + this.setState({ currentIndex: i }); + } + + handlePrev = () => { + this.setState(({ currentIndex }) => ({ + currentIndex: Math.max(0, currentIndex - 1), + })); + } + + handleNext = () => { + const { pages } = this; + + this.setState(({ currentIndex }) => ({ + currentIndex: Math.min(currentIndex + 1, pages.length - 1), + })); + } + + handleSwipe = (index) => { + this.setState({ currentIndex: index }); + } + + handleFinish = () => { + this.props.dispatch(closeOnboarding()); + } + + handleKeyUp = ({ key }) => { + switch (key) { + case 'ArrowLeft': + this.handlePrev(); + break; + case 'ArrowRight': + this.handleNext(); + break; + } + } + + render () { + const { currentIndex } = this.state; + const { pages } = this; + + return ( + <div className='introduction'> + <ReactSwipeableViews index={currentIndex} onChangeIndex={this.handleSwipe} className='introduction__pager'> + {pages.map((page, i) => ( + <div key={i} className={classNames('introduction__frame-wrapper', { 'active': i === currentIndex })}>{page}</div> + ))} + </ReactSwipeableViews> + + <div className='introduction__dots'> + {pages.map((_, i) => ( + <div + key={`dot-${i}`} + role='button' + tabIndex='0' + data-index={i} + onClick={this.handleDot} + className={classNames('introduction__dot', { active: i === currentIndex })} + /> + ))} + </div> + </div> + ); + } + +} diff --git a/app/javascript/mastodon/features/notifications/components/column_settings.js b/app/javascript/mastodon/features/notifications/components/column_settings.js index fcdf5c6e6..a334fd63c 100644 --- a/app/javascript/mastodon/features/notifications/components/column_settings.js +++ b/app/javascript/mastodon/features/notifications/components/column_settings.js @@ -21,9 +21,11 @@ export default class ColumnSettings extends React.PureComponent { render () { const { settings, pushSettings, onChange, onClear } = this.props; - const alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />; - const showStr = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />; - const soundStr = <FormattedMessage id='notifications.column_settings.sound' defaultMessage='Play sound' />; + const filterShowStr = <FormattedMessage id='notifications.column_settings.filter_bar.show' defaultMessage='Show' />; + const filterAdvancedStr = <FormattedMessage id='notifications.column_settings.filter_bar.advanced' defaultMessage='Display all categories' />; + const alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />; + const showStr = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />; + const soundStr = <FormattedMessage id='notifications.column_settings.sound' defaultMessage='Play sound' />; const showPushSettings = pushSettings.get('browserSupport') && pushSettings.get('isSubscribed'); const pushStr = showPushSettings && <FormattedMessage id='notifications.column_settings.push' defaultMessage='Push notifications' />; @@ -34,6 +36,16 @@ export default class ColumnSettings extends React.PureComponent { <ClearColumnButton onClick={onClear} /> </div> + <div role='group' aria-labelledby='notifications-filter-bar'> + <span id='notifications-filter-bar' className='column-settings__section'> + <FormattedMessage id='notifications.column_settings.filter_bar.category' defaultMessage='Quick filter bar' /> + </span> + <div className='column-settings__row'> + <SettingToggle id='show-filter-bar' prefix='notifications' settings={settings} settingPath={['quickFilter', 'show']} onChange={onChange} label={filterShowStr} /> + <SettingToggle id='show-filter-bar' prefix='notifications' settings={settings} settingPath={['quickFilter', 'advanced']} onChange={onChange} label={filterAdvancedStr} /> + </div> + </div> + <div role='group' aria-labelledby='notifications-follow'> <span id='notifications-follow' className='column-settings__section'><FormattedMessage id='notifications.column_settings.follow' defaultMessage='New followers:' /></span> diff --git a/app/javascript/mastodon/features/notifications/components/filter_bar.js b/app/javascript/mastodon/features/notifications/components/filter_bar.js new file mode 100644 index 000000000..f95a2c9de --- /dev/null +++ b/app/javascript/mastodon/features/notifications/components/filter_bar.js @@ -0,0 +1,93 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; + +const tooltips = defineMessages({ + mentions: { id: 'notifications.filter.mentions', defaultMessage: 'Mentions' }, + favourites: { id: 'notifications.filter.favourites', defaultMessage: 'Favourites' }, + boosts: { id: 'notifications.filter.boosts', defaultMessage: 'Boosts' }, + follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' }, +}); + +export default @injectIntl +class FilterBar extends React.PureComponent { + + static propTypes = { + selectFilter: PropTypes.func.isRequired, + selectedFilter: PropTypes.string.isRequired, + advancedMode: PropTypes.bool.isRequired, + intl: PropTypes.object.isRequired, + }; + + onClick (notificationType) { + return () => this.props.selectFilter(notificationType); + } + + render () { + const { selectedFilter, advancedMode, intl } = this.props; + const renderedElement = !advancedMode ? ( + <div className='notification__filter-bar'> + <button + className={selectedFilter === 'all' ? 'active' : ''} + onClick={this.onClick('all')} + > + <FormattedMessage + id='notifications.filter.all' + defaultMessage='All' + /> + </button> + <button + className={selectedFilter === 'mention' ? 'active' : ''} + onClick={this.onClick('mention')} + > + <FormattedMessage + id='notifications.filter.mentions' + defaultMessage='Mentions' + /> + </button> + </div> + ) : ( + <div className='notification__filter-bar'> + <button + className={selectedFilter === 'all' ? 'active' : ''} + onClick={this.onClick('all')} + > + <FormattedMessage + id='notifications.filter.all' + defaultMessage='All' + /> + </button> + <button + className={selectedFilter === 'mention' ? 'active' : ''} + onClick={this.onClick('mention')} + title={intl.formatMessage(tooltips.mentions)} + > + <i className='fa fa-fw fa-at' /> + </button> + <button + className={selectedFilter === 'favourite' ? 'active' : ''} + onClick={this.onClick('favourite')} + title={intl.formatMessage(tooltips.favourites)} + > + <i className='fa fa-fw fa-star' /> + </button> + <button + className={selectedFilter === 'reblog' ? 'active' : ''} + onClick={this.onClick('reblog')} + title={intl.formatMessage(tooltips.boosts)} + > + <i className='fa fa-fw fa-retweet' /> + </button> + <button + className={selectedFilter === 'follow' ? 'active' : ''} + onClick={this.onClick('follow')} + title={intl.formatMessage(tooltips.follows)} + > + <i className='fa fa-fw fa-user-plus' /> + </button> + </div> + ); + return renderedElement; + } + +} diff --git a/app/javascript/mastodon/features/notifications/containers/column_settings_container.js b/app/javascript/mastodon/features/notifications/containers/column_settings_container.js index e9cef0a7b..a67f26295 100644 --- a/app/javascript/mastodon/features/notifications/containers/column_settings_container.js +++ b/app/javascript/mastodon/features/notifications/containers/column_settings_container.js @@ -2,6 +2,7 @@ import { connect } from 'react-redux'; import { defineMessages, injectIntl } from 'react-intl'; import ColumnSettings from '../components/column_settings'; import { changeSetting } from '../../../actions/settings'; +import { setFilter } from '../../../actions/notifications'; import { clearNotifications } from '../../../actions/notifications'; import { changeAlerts as changePushNotifications } from '../../../actions/push_notifications'; import { openModal } from '../../../actions/modal'; @@ -21,6 +22,9 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ onChange (path, checked) { if (path[0] === 'push') { dispatch(changePushNotifications(path.slice(1), checked)); + } else if (path[0] === 'quickFilter') { + dispatch(changeSetting(['notifications', ...path], checked)); + dispatch(setFilter('all')); } else { dispatch(changeSetting(['notifications', ...path], checked)); } diff --git a/app/javascript/mastodon/features/notifications/containers/filter_bar_container.js b/app/javascript/mastodon/features/notifications/containers/filter_bar_container.js new file mode 100644 index 000000000..4d495c290 --- /dev/null +++ b/app/javascript/mastodon/features/notifications/containers/filter_bar_container.js @@ -0,0 +1,16 @@ +import { connect } from 'react-redux'; +import FilterBar from '../components/filter_bar'; +import { setFilter } from '../../../actions/notifications'; + +const makeMapStateToProps = state => ({ + selectedFilter: state.getIn(['settings', 'notifications', 'quickFilter', 'active']), + advancedMode: state.getIn(['settings', 'notifications', 'quickFilter', 'advanced']), +}); + +const mapDispatchToProps = (dispatch) => ({ + selectFilter (newActiveFilter) { + dispatch(setFilter(newActiveFilter)); + }, +}); + +export default connect(makeMapStateToProps, mapDispatchToProps)(FilterBar); diff --git a/app/javascript/mastodon/features/notifications/index.js b/app/javascript/mastodon/features/notifications/index.js index aa82dbbb9..9430b2050 100644 --- a/app/javascript/mastodon/features/notifications/index.js +++ b/app/javascript/mastodon/features/notifications/index.js @@ -9,6 +9,7 @@ import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import NotificationContainer from './containers/notification_container'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ColumnSettingsContainer from './containers/column_settings_container'; +import FilterBarContainer from './containers/filter_bar_container'; import { createSelector } from 'reselect'; import { List as ImmutableList } from 'immutable'; import { debounce } from 'lodash'; @@ -20,11 +21,22 @@ const messages = defineMessages({ }); const getNotifications = createSelector([ + state => state.getIn(['settings', 'notifications', 'quickFilter', 'show']), + state => state.getIn(['settings', 'notifications', 'quickFilter', 'active']), state => ImmutableList(state.getIn(['settings', 'notifications', 'shows']).filter(item => !item).keys()), state => state.getIn(['notifications', 'items']), -], (excludedTypes, notifications) => notifications.filterNot(item => item !== null && excludedTypes.includes(item.get('type')))); +], (showFilterBar, allowedType, excludedTypes, notifications) => { + if (!showFilterBar || allowedType === 'all') { + // used if user changed the notification settings after loading the notifications from the server + // otherwise a list of notifications will come pre-filtered from the backend + // we need to turn it off for FilterBar in order not to block ourselves from seeing a specific category + return notifications.filterNot(item => item !== null && excludedTypes.includes(item.get('type'))); + } + return notifications.filter(item => item !== null && allowedType === item.get('type')); +}); const mapStateToProps = state => ({ + showFilterBar: state.getIn(['settings', 'notifications', 'quickFilter', 'show']), notifications: getNotifications(state), isLoading: state.getIn(['notifications', 'isLoading'], true), isUnread: state.getIn(['notifications', 'unread']) > 0, @@ -38,6 +50,7 @@ class Notifications extends React.PureComponent { static propTypes = { columnId: PropTypes.string, notifications: ImmutablePropTypes.list.isRequired, + showFilterBar: PropTypes.bool.isRequired, dispatch: PropTypes.func.isRequired, shouldUpdateScroll: PropTypes.func, intl: PropTypes.object.isRequired, @@ -117,12 +130,16 @@ class Notifications extends React.PureComponent { } render () { - const { intl, notifications, shouldUpdateScroll, isLoading, isUnread, columnId, multiColumn, hasMore } = this.props; + const { intl, notifications, shouldUpdateScroll, isLoading, isUnread, columnId, multiColumn, hasMore, showFilterBar } = this.props; const pinned = !!columnId; const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. Interact with others to start the conversation." />; let scrollableContent = null; + const filterBarContainer = showFilterBar + ? (<FilterBarContainer />) + : null; + if (isLoading && this.scrollableContent) { scrollableContent = this.scrollableContent; } else if (notifications.size > 0 || hasMore) { @@ -179,7 +196,7 @@ class Notifications extends React.PureComponent { > <ColumnSettingsContainer /> </ColumnHeader> - + {filterBarContainer} {scrollContainer} </Column> ); diff --git a/app/javascript/mastodon/features/ui/components/modal_root.js b/app/javascript/mastodon/features/ui/components/modal_root.js index b3b1ea862..cc2ab6c8c 100644 --- a/app/javascript/mastodon/features/ui/components/modal_root.js +++ b/app/javascript/mastodon/features/ui/components/modal_root.js @@ -11,7 +11,6 @@ import BoostModal from './boost_modal'; import ConfirmationModal from './confirmation_modal'; import FocalPointModal from './focal_point_modal'; import { - OnboardingModal, MuteModal, ReportModal, EmbedModal, @@ -21,7 +20,6 @@ import { const MODAL_COMPONENTS = { 'MEDIA': () => Promise.resolve({ default: MediaModal }), - 'ONBOARDING': OnboardingModal, 'VIDEO': () => Promise.resolve({ default: VideoModal }), 'BOOST': () => Promise.resolve({ default: BoostModal }), 'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }), diff --git a/app/javascript/mastodon/features/ui/components/onboarding_modal.js b/app/javascript/mastodon/features/ui/components/onboarding_modal.js deleted file mode 100644 index 4a5b249c9..000000000 --- a/app/javascript/mastodon/features/ui/components/onboarding_modal.js +++ /dev/null @@ -1,324 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import ReactSwipeableViews from 'react-swipeable-views'; -import classNames from 'classnames'; -import Permalink from '../../../components/permalink'; -import ComposeForm from '../../compose/components/compose_form'; -import Search from '../../compose/components/search'; -import NavigationBar from '../../compose/components/navigation_bar'; -import ColumnHeader from './column_header'; -import { List as ImmutableList } from 'immutable'; -import { me } from '../../../initial_state'; - -const noop = () => { }; - -const messages = defineMessages({ - home_title: { id: 'column.home', defaultMessage: 'Home' }, - notifications_title: { id: 'column.notifications', defaultMessage: 'Notifications' }, - local_title: { id: 'column.community', defaultMessage: 'Local timeline' }, - federated_title: { id: 'column.public', defaultMessage: 'Federated timeline' }, -}); - -const PageOne = ({ acct, domain }) => ( - <div className='onboarding-modal__page onboarding-modal__page-one'> - <div className='onboarding-modal__page-one__lead'> - <h1><FormattedMessage id='onboarding.page_one.welcome' defaultMessage='Welcome to Mastodon!' /></h1> - <p><FormattedMessage id='onboarding.page_one.federation' defaultMessage='Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.' /></p> - </div> - - <div className='onboarding-modal__page-one__extra'> - <div className='display-case'> - <div className='display-case__label'> - <FormattedMessage id='onboarding.page_one.full_handle' defaultMessage='Your full handle' /> - </div> - - <div className='display-case__case'> - @{acct}@{domain} - </div> - </div> - - <p><FormattedMessage id='onboarding.page_one.handle_hint' defaultMessage='This is what you would tell your friends to search for.' /></p> - </div> - </div> -); - -PageOne.propTypes = { - acct: PropTypes.string.isRequired, - domain: PropTypes.string.isRequired, -}; - -const PageTwo = ({ myAccount }) => ( - <div className='onboarding-modal__page onboarding-modal__page-two'> - <div className='figure non-interactive'> - <div className='pseudo-drawer'> - <NavigationBar account={myAccount} /> - - <ComposeForm - text='Awoo! #introductions' - suggestions={ImmutableList()} - mentionedDomains={[]} - spoiler={false} - onChange={noop} - onSubmit={noop} - onPaste={noop} - onPickEmoji={noop} - onChangeSpoilerText={noop} - onClearSuggestions={noop} - onFetchSuggestions={noop} - onSuggestionSelected={noop} - showSearch - /> - </div> - </div> - - <p><FormattedMessage id='onboarding.page_two.compose' defaultMessage='Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.' /></p> - </div> -); - -PageTwo.propTypes = { - myAccount: ImmutablePropTypes.map.isRequired, -}; - -const PageThree = ({ myAccount }) => ( - <div className='onboarding-modal__page onboarding-modal__page-three'> - <div className='figure non-interactive'> - <Search - value='' - onChange={noop} - onSubmit={noop} - onClear={noop} - onShow={noop} - /> - - <div className='pseudo-drawer'> - <NavigationBar account={myAccount} /> - </div> - </div> - - <p><FormattedMessage id='onboarding.page_three.search' defaultMessage='Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.' values={{ illustration: <Permalink to='/timelines/tag/illustration' href='/tags/illustration'>#illustration</Permalink>, introductions: <Permalink to='/timelines/tag/introductions' href='/tags/introductions'>#introductions</Permalink> }} /></p> - <p><FormattedMessage id='onboarding.page_three.profile' defaultMessage='Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.' /></p> - </div> -); - -PageThree.propTypes = { - myAccount: ImmutablePropTypes.map.isRequired, -}; - -const PageFour = ({ domain, intl }) => ( - <div className='onboarding-modal__page onboarding-modal__page-four'> - <div className='onboarding-modal__page-four__columns'> - <div className='row'> - <div> - <div className='figure non-interactive'><ColumnHeader icon='home' type={intl.formatMessage(messages.home_title)} /></div> - <p><FormattedMessage id='onboarding.page_four.home' defaultMessage='The home timeline shows posts from people you follow.' /></p> - </div> - - <div> - <div className='figure non-interactive'><ColumnHeader icon='bell' type={intl.formatMessage(messages.notifications_title)} /></div> - <p><FormattedMessage id='onboarding.page_four.notifications' defaultMessage='The notifications column shows when someone interacts with you.' /></p> - </div> - </div> - - <div className='row'> - <div> - <div className='figure non-interactive' style={{ marginBottom: 0 }}><ColumnHeader icon='users' type={intl.formatMessage(messages.local_title)} /></div> - </div> - - <div> - <div className='figure non-interactive' style={{ marginBottom: 0 }}><ColumnHeader icon='globe' type={intl.formatMessage(messages.federated_title)} /></div> - </div> - </div> - - <p><FormattedMessage id='onboarding.page_five.public_timelines' defaultMessage='The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.' values={{ domain }} /></p> - </div> - </div> -); - -PageFour.propTypes = { - domain: PropTypes.string.isRequired, - intl: PropTypes.object.isRequired, -}; - -const PageSix = ({ admin, domain }) => { - let adminSection = ''; - - if (admin) { - adminSection = ( - <p> - <FormattedMessage id='onboarding.page_six.admin' defaultMessage="Your instance's admin is {admin}." values={{ admin: <Permalink href={admin.get('url')} to={`/accounts/${admin.get('id')}`}>@{admin.get('acct')}</Permalink> }} /> - <br /> - <FormattedMessage id='onboarding.page_six.read_guidelines' defaultMessage="Please read {domain}'s {guidelines}!" values={{ domain, guidelines: <a href='/about/more' target='_blank'><FormattedMessage id='onboarding.page_six.guidelines' defaultMessage='community guidelines' /></a> }} /> - </p> - ); - } - - return ( - <div className='onboarding-modal__page onboarding-modal__page-six'> - <h1><FormattedMessage id='onboarding.page_six.almost_done' defaultMessage='Almost done...' /></h1> - {adminSection} - <p><FormattedMessage id='onboarding.page_six.github' defaultMessage='Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.' values={{ github: <a href='https://github.com/tootsuite/mastodon' target='_blank' rel='noopener'>GitHub</a> }} /></p> - <p><FormattedMessage id='onboarding.page_six.apps_available' defaultMessage='There are {apps} available for iOS, Android and other platforms.' values={{ apps: <a href='https://joinmastodon.org/apps' target='_blank' rel='noopener'><FormattedMessage id='onboarding.page_six.various_app' defaultMessage='mobile apps' /></a> }} /></p> - <p><em><FormattedMessage id='onboarding.page_six.appetoot' defaultMessage='Bon Appetoot!' /></em></p> - </div> - ); -}; - -PageSix.propTypes = { - admin: ImmutablePropTypes.map, - domain: PropTypes.string.isRequired, -}; - -const mapStateToProps = state => ({ - myAccount: state.getIn(['accounts', me]), - admin: state.getIn(['accounts', state.getIn(['meta', 'admin'])]), - domain: state.getIn(['meta', 'domain']), -}); - -export default @connect(mapStateToProps) -@injectIntl -class OnboardingModal extends React.PureComponent { - - static propTypes = { - onClose: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - myAccount: ImmutablePropTypes.map.isRequired, - domain: PropTypes.string.isRequired, - admin: ImmutablePropTypes.map, - }; - - state = { - currentIndex: 0, - }; - - componentWillMount() { - const { myAccount, admin, domain, intl } = this.props; - this.pages = [ - <PageOne acct={myAccount.get('acct')} domain={domain} />, - <PageTwo myAccount={myAccount} />, - <PageThree myAccount={myAccount} />, - <PageFour domain={domain} intl={intl} />, - <PageSix admin={admin} domain={domain} />, - ]; - }; - - componentDidMount() { - window.addEventListener('keyup', this.handleKeyUp); - } - - componentWillUnmount() { - window.addEventListener('keyup', this.handleKeyUp); - } - - handleSkip = (e) => { - e.preventDefault(); - this.props.onClose(); - } - - handleDot = (e) => { - const i = Number(e.currentTarget.getAttribute('data-index')); - e.preventDefault(); - this.setState({ currentIndex: i }); - } - - handlePrev = () => { - this.setState(({ currentIndex }) => ({ - currentIndex: Math.max(0, currentIndex - 1), - })); - } - - handleNext = () => { - const { pages } = this; - this.setState(({ currentIndex }) => ({ - currentIndex: Math.min(currentIndex + 1, pages.length - 1), - })); - } - - handleSwipe = (index) => { - this.setState({ currentIndex: index }); - } - - handleKeyUp = ({ key }) => { - switch (key) { - case 'ArrowLeft': - this.handlePrev(); - break; - case 'ArrowRight': - this.handleNext(); - break; - } - } - - handleClose = () => { - this.props.onClose(); - } - - render () { - const { pages } = this; - const { currentIndex } = this.state; - const hasMore = currentIndex < pages.length - 1; - - const nextOrDoneBtn = hasMore ? ( - <button onClick={this.handleNext} className='onboarding-modal__nav onboarding-modal__next shake-bottom'> - <FormattedMessage id='onboarding.next' defaultMessage='Next' /> <i className='fa fa-fw fa-chevron-right' /> - </button> - ) : ( - <button onClick={this.handleClose} className='onboarding-modal__nav onboarding-modal__done shake-bottom'> - <FormattedMessage id='onboarding.done' defaultMessage='Done' /> <i className='fa fa-fw fa-check' /> - </button> - ); - - return ( - <div className='modal-root__modal onboarding-modal'> - <ReactSwipeableViews index={currentIndex} onChangeIndex={this.handleSwipe} className='onboarding-modal__pager'> - {pages.map((page, i) => { - const className = classNames('onboarding-modal__page__wrapper', `onboarding-modal__page__wrapper-${i}`, { - 'onboarding-modal__page__wrapper--active': i === currentIndex, - }); - - return ( - <div key={i} className={className}>{page}</div> - ); - })} - </ReactSwipeableViews> - - <div className='onboarding-modal__paginator'> - <div> - <button - onClick={this.handleSkip} - className='onboarding-modal__nav onboarding-modal__skip' - > - <FormattedMessage id='onboarding.skip' defaultMessage='Skip' /> - </button> - </div> - - <div className='onboarding-modal__dots'> - {pages.map((_, i) => { - const className = classNames('onboarding-modal__dot', { - active: i === currentIndex, - }); - - return ( - <div - key={`dot-${i}`} - role='button' - tabIndex='0' - data-index={i} - onClick={this.handleDot} - className={className} - /> - ); - })} - </div> - - <div> - {nextOrDoneBtn} - </div> - </div> - </div> - ); - } - -} diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index 662375a76..e11235a81 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -294,6 +294,7 @@ class UI extends React.PureComponent { componentWillMount () { window.addEventListener('beforeunload', this.handleBeforeUnload, false); + document.addEventListener('dragenter', this.handleDragEnter, false); document.addEventListener('dragover', this.handleDragOver, false); document.addEventListener('drop', this.handleDrop, false); @@ -304,8 +305,13 @@ class UI extends React.PureComponent { navigator.serviceWorker.addEventListener('message', this.handleServiceWorkerPostMessage); } + if (typeof window.Notification !== 'undefined' && Notification.permission === 'default') { + window.setTimeout(() => Notification.requestPermission(), 120 * 1000); + } + this.props.dispatch(expandHomeTimeline()); this.props.dispatch(expandNotifications()); + setTimeout(() => this.props.dispatch(fetchFilters()), 500); } diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js index 2a15c052f..235fd2a07 100644 --- a/app/javascript/mastodon/features/ui/util/async-components.js +++ b/app/javascript/mastodon/features/ui/util/async-components.js @@ -102,10 +102,6 @@ export function Mutes () { return import(/* webpackChunkName: "features/mutes" */'../../mutes'); } -export function OnboardingModal () { - return import(/* webpackChunkName: "modals/onboarding_modal" */'../components/onboarding_modal'); -} - export function MuteModal () { return import(/* webpackChunkName: "modals/mute_modal" */'../components/mute_modal'); } diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index ca5abcb05..d20ac1f88 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -228,6 +228,14 @@ "notification.reblog": "{name} boosted your status", "notifications.clear": "Clear notifications", "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?", + "notifications.filter.all": "All", + "notifications.filter.mentions": "Mentions", + "notifications.filter.favourites": "Favourites", + "notifications.filter.boosts": "Boosts", + "notifications.filter.follows": "Follows", + "notifications.column_settings.filter_bar.category": "Quick filter bar", + "notifications.column_settings.filter_bar.show": "Show", + "notifications.column_settings.filter_bar.advanced": "Display all categories", "notifications.column_settings.alert": "Desktop notifications", "notifications.column_settings.favourite": "Favourites:", "notifications.column_settings.follow": "New followers:", diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index 237ffd33a..356697024 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -228,6 +228,14 @@ "notification.reblog": "{name} podbił(a) Twój wpis", "notifications.clear": "Wyczyść powiadomienia", "notifications.clear_confirmation": "Czy na pewno chcesz bezpowrotnie usunąć wszystkie powiadomienia?", + "notifications.filter.all": "Wszystkie", + "notifications.filter.mentions": "Wspomnienia", + "notifications.filter.favourites": "Ulubione", + "notifications.filter.boosts": "Podbicia", + "notifications.filter.follows": "Śledzenia", + "notifications.column_settings.filter_bar.category": "Szybkie filtrowanie", + "notifications.column_settings.filter_bar.show": "Pokaż", + "notifications.column_settings.filter_bar.advanced": "Wyświetl wszystkie kategorie", "notifications.column_settings.alert": "Powiadomienia na pulpicie", "notifications.column_settings.favourite": "Dodanie do ulubionych:", "notifications.column_settings.follow": "Nowi śledzący:", diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index 67d55f66f..1622871b8 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -51,6 +51,7 @@ const initialState = ImmutableMap({ in_reply_to: null, is_composing: false, is_submitting: false, + is_changing_upload: false, is_uploading: false, progress: 0, media_attachments: ImmutableList(), @@ -79,6 +80,7 @@ function clearAll(state) { map.set('spoiler', false); map.set('spoiler_text', ''); map.set('is_submitting', false); + map.set('is_changing_upload', false); map.set('in_reply_to', null); map.set('privacy', state.get('default_privacy')); map.set('sensitive', false); @@ -248,13 +250,15 @@ export default function compose(state = initialState, action) { map.set('idempotencyKey', uuid()); }); case COMPOSE_SUBMIT_REQUEST: - case COMPOSE_UPLOAD_CHANGE_REQUEST: return state.set('is_submitting', true); + case COMPOSE_UPLOAD_CHANGE_REQUEST: + return state.set('is_changing_upload', true); case COMPOSE_SUBMIT_SUCCESS: return clearAll(state); case COMPOSE_SUBMIT_FAIL: - case COMPOSE_UPLOAD_CHANGE_FAIL: return state.set('is_submitting', false); + case COMPOSE_UPLOAD_CHANGE_FAIL: + return state.set('is_changing_upload', false); case COMPOSE_UPLOAD_REQUEST: return state.set('is_uploading', true); case COMPOSE_UPLOAD_SUCCESS: @@ -300,7 +304,7 @@ export default function compose(state = initialState, action) { return insertEmoji(state, action.position, action.emoji, action.needsSpace); case COMPOSE_UPLOAD_CHANGE_SUCCESS: return state - .set('is_submitting', false) + .set('is_changing_upload', false) .update('media_attachments', list => list.map(item => { if (item.get('id') === action.media.id) { return fromJS(action.media); diff --git a/app/javascript/mastodon/reducers/notifications.js b/app/javascript/mastodon/reducers/notifications.js index d71ae00ae..19a02f5b1 100644 --- a/app/javascript/mastodon/reducers/notifications.js +++ b/app/javascript/mastodon/reducers/notifications.js @@ -3,6 +3,7 @@ import { NOTIFICATIONS_EXPAND_SUCCESS, NOTIFICATIONS_EXPAND_REQUEST, NOTIFICATIONS_EXPAND_FAIL, + NOTIFICATIONS_FILTER_SET, NOTIFICATIONS_CLEAR, NOTIFICATIONS_SCROLL_TOP, } from '../actions/notifications'; @@ -98,6 +99,8 @@ export default function notifications(state = initialState, action) { return state.set('isLoading', true); case NOTIFICATIONS_EXPAND_FAIL: return state.set('isLoading', false); + case NOTIFICATIONS_FILTER_SET: + return state.set('items', ImmutableList()).set('hasMore', true); case NOTIFICATIONS_SCROLL_TOP: return updateTop(state, action.top); case NOTIFICATIONS_UPDATE: diff --git a/app/javascript/mastodon/reducers/settings.js b/app/javascript/mastodon/reducers/settings.js index 12bcc2583..2e1878cf7 100644 --- a/app/javascript/mastodon/reducers/settings.js +++ b/app/javascript/mastodon/reducers/settings.js @@ -1,4 +1,5 @@ import { SETTING_CHANGE, SETTING_SAVE } from '../actions/settings'; +import { NOTIFICATIONS_FILTER_SET } from '../actions/notifications'; import { COLUMN_ADD, COLUMN_REMOVE, COLUMN_MOVE, COLUMN_PARAMS_CHANGE } from '../actions/columns'; import { STORE_HYDRATE } from '../actions/store'; import { EMOJI_USE } from '../actions/emojis'; @@ -32,6 +33,12 @@ const initialState = ImmutableMap({ mention: true, }), + quickFilter: ImmutableMap({ + active: 'all', + show: true, + advanced: false, + }), + shows: ImmutableMap({ follow: true, favourite: true, @@ -112,6 +119,7 @@ export default function settings(state = initialState, action) { switch(action.type) { case STORE_HYDRATE: return hydrate(state, action.state.get('settings')); + case NOTIFICATIONS_FILTER_SET: case SETTING_CHANGE: return state .setIn(action.path, action.value) diff --git a/app/javascript/styles/application.scss b/app/javascript/styles/application.scss index 0990a4f25..4bce74187 100644 --- a/app/javascript/styles/application.scss +++ b/app/javascript/styles/application.scss @@ -16,6 +16,7 @@ @import 'mastodon/stream_entries'; @import 'mastodon/boost'; @import 'mastodon/components'; +@import 'mastodon/introduction'; @import 'mastodon/modal'; @import 'mastodon/emoji_picker'; @import 'mastodon/about'; diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 308429573..61e330a26 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -2107,6 +2107,7 @@ a.account__display-name { &__append { flex: 1 1 auto; position: relative; + min-height: 120px; } } @@ -2900,7 +2901,6 @@ a.status-card.compact:hover { transform: translateX(-50%); margin: 82px 0 0 50%; white-space: nowrap; - animation: loader-label 1.15s infinite cubic-bezier(0.215, 0.610, 0.355, 1.000); } } @@ -2909,11 +2909,20 @@ a.status-card.compact:hover { top: 50%; left: 50%; transform: translate(-50%, -50%); - width: 0; - height: 0; + width: 42px; + height: 42px; box-sizing: border-box; + background-color: transparent; border: 0 solid lighten($ui-base-color, 26%); + border-width: 6px; border-radius: 50%; +} + +.no-reduce-motion .loading-indicator span { + animation: loader-label 1.15s infinite cubic-bezier(0.215, 0.610, 0.355, 1.000); +} + +.no-reduce-motion .loading-indicator__figure { animation: loader-figure 1.15s infinite cubic-bezier(0.215, 0.610, 0.355, 1.000); } @@ -3789,25 +3798,6 @@ a.status-card.compact:hover { flex-direction: column; } -.onboarding-modal__pager { - height: 80vh; - width: 80vw; - max-width: 520px; - max-height: 470px; - - .react-swipeable-view-container > div { - width: 100%; - height: 100%; - box-sizing: border-box; - display: none; - flex-direction: column; - align-items: center; - justify-content: center; - display: flex; - user-select: text; - } -} - .error-modal__body { height: 80vh; width: 80vw; @@ -3841,22 +3831,6 @@ a.status-card.compact:hover { text-align: center; } -@media screen and (max-width: 550px) { - .onboarding-modal { - width: 100%; - height: 100%; - border-radius: 0; - } - - .onboarding-modal__pager { - width: 100%; - height: auto; - max-width: none; - max-height: none; - flex: 1 1 auto; - } -} - .onboarding-modal__paginator, .error-modal__footer { flex: 0 0 auto; @@ -3905,124 +3879,6 @@ a.status-card.compact:hover { justify-content: center; } -.onboarding-modal__dots { - flex: 1 1 auto; - display: flex; - align-items: center; - justify-content: center; -} - -.onboarding-modal__dot { - width: 14px; - height: 14px; - border-radius: 14px; - background: darken($ui-secondary-color, 16%); - margin: 0 3px; - cursor: pointer; - - &:hover { - background: darken($ui-secondary-color, 18%); - } - - &.active { - cursor: default; - background: darken($ui-secondary-color, 24%); - } -} - -.onboarding-modal__page__wrapper { - pointer-events: none; - padding: 25px; - padding-bottom: 0; - - &.onboarding-modal__page__wrapper--active { - pointer-events: auto; - } -} - -.onboarding-modal__page { - cursor: default; - line-height: 21px; - - h1 { - font-size: 18px; - font-weight: 500; - color: $inverted-text-color; - margin-bottom: 20px; - } - - a { - color: $highlight-text-color; - - &:hover, - &:focus, - &:active { - color: lighten($highlight-text-color, 4%); - } - } - - .navigation-bar a { - color: inherit; - } - - p { - font-size: 16px; - color: $lighter-text-color; - margin-top: 10px; - margin-bottom: 10px; - - &:last-child { - margin-bottom: 0; - } - - strong { - font-weight: 500; - background: $ui-base-color; - color: $secondary-text-color; - border-radius: 4px; - font-size: 14px; - padding: 3px 6px; - - @each $lang in $cjk-langs { - &:lang(#{$lang}) { - font-weight: 700; - } - } - } - } -} - -.onboarding-modal__page__wrapper-0 { - background: url('~images/elephant_ui_greeting.svg') no-repeat left bottom / auto 250px; - height: 100%; - padding: 0; -} - -.onboarding-modal__page-one { - &__lead { - padding: 65px; - padding-top: 45px; - padding-bottom: 0; - margin-bottom: 10px; - - h1 { - font-size: 26px; - line-height: 36px; - margin-bottom: 8px; - } - - p { - margin-bottom: 0; - } - } - - &__extra { - padding-right: 65px; - padding-left: 185px; - text-align: center; - } -} - .display-case { text-align: center; font-size: 15px; @@ -4045,92 +3901,6 @@ a.status-card.compact:hover { } } -.onboarding-modal__page-two, -.onboarding-modal__page-three, -.onboarding-modal__page-four, -.onboarding-modal__page-five { - p { - text-align: left; - } - - .figure { - background: darken($ui-base-color, 8%); - color: $secondary-text-color; - margin-bottom: 20px; - border-radius: 4px; - padding: 10px; - text-align: center; - font-size: 14px; - box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.3); - - .onboarding-modal__image { - border-radius: 4px; - margin-bottom: 10px; - } - - &.non-interactive { - pointer-events: none; - text-align: left; - } - } -} - -.onboarding-modal__page-four__columns { - .row { - display: flex; - margin-bottom: 20px; - - & > div { - flex: 1 1 0; - margin: 0 10px; - - &:first-child { - margin-left: 0; - } - - &:last-child { - margin-right: 0; - } - - p { - text-align: center; - } - } - - &:last-child { - margin-bottom: 0; - } - } - - .column-header { - color: $primary-text-color; - } -} - -@media screen and (max-width: 320px) and (max-height: 600px) { - .onboarding-modal__page p { - font-size: 14px; - line-height: 20px; - } - - .onboarding-modal__page-two .figure, - .onboarding-modal__page-three .figure, - .onboarding-modal__page-four .figure, - .onboarding-modal__page-five .figure { - font-size: 12px; - margin-bottom: 10px; - } - - .onboarding-modal__page-four__columns .row { - margin-bottom: 10px; - } - - .onboarding-modal__page-four__columns .column-header { - padding: 5px; - font-size: 12px; - } -} - .onboard-sliders { display: inline-block; max-width: 30px; @@ -5030,12 +4800,19 @@ a.status-card.compact:hover { } } +.notification__filter-bar, .account__section-headline { background: darken($ui-base-color, 4%); border-bottom: 1px solid lighten($ui-base-color, 8%); cursor: default; display: flex; + button { + background: darken($ui-base-color, 4%); + border: 0; + } + + button, a { display: block; flex: 1 1 auto; diff --git a/app/javascript/styles/mastodon/containers.scss b/app/javascript/styles/mastodon/containers.scss index 44fc1e538..8de53ca98 100644 --- a/app/javascript/styles/mastodon/containers.scss +++ b/app/javascript/styles/mastodon/containers.scss @@ -294,6 +294,12 @@ text-decoration: underline; color: $primary-text-color; } + + @media screen and (max-width: $no-gap-breakpoint) { + &.optional { + display: none; + } + } } .nav-button { diff --git a/app/javascript/styles/mastodon/introduction.scss b/app/javascript/styles/mastodon/introduction.scss new file mode 100644 index 000000000..222d8f60e --- /dev/null +++ b/app/javascript/styles/mastodon/introduction.scss @@ -0,0 +1,153 @@ +.introduction { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + @media screen and (max-width: 920px) { + background: darken($ui-base-color, 8%); + display: block !important; + } + + &__pager { + background: darken($ui-base-color, 8%); + box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); + overflow: hidden; + } + + &__pager, + &__frame { + border-radius: 10px; + width: 50vw; + min-width: 920px; + + @media screen and (max-width: 920px) { + min-width: 0; + width: 100%; + border-radius: 0; + box-shadow: none; + } + } + + &__frame-wrapper { + opacity: 0; + transition: opacity 500ms linear; + + &.active { + opacity: 1; + transition: opacity 50ms linear; + } + } + + &__frame { + overflow: hidden; + } + + &__illustration { + height: 50vh; + + @media screen and (max-width: 630px) { + height: auto; + } + + img { + object-fit: cover; + display: block; + margin: 0; + width: 100%; + height: 100%; + } + } + + &__text { + border-top: 2px solid $ui-highlight-color; + + &--columnized { + display: flex; + + & > div { + flex: 1 1 33.33%; + text-align: center; + padding: 25px; + padding-bottom: 30px; + } + + @media screen and (max-width: 630px) { + display: block; + padding: 15px 0; + padding-bottom: 20px; + + & > div { + padding: 10px 25px; + } + } + } + + 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; + + code { + display: inline-block; + background: darken($ui-base-color, 8%); + font-size: 15px; + border: 1px solid lighten($ui-base-color, 8%); + border-radius: 2px; + padding: 1px 3px; + } + } + + &--centered { + padding: 25px; + padding-bottom: 30px; + text-align: center; + } + } + + &__dots { + display: flex; + align-items: center; + justify-content: center; + padding: 25px; + + @media screen and (max-width: 630px) { + display: none; + } + } + + &__dot { + width: 14px; + height: 14px; + border-radius: 14px; + border: 1px solid $ui-highlight-color; + background: transparent; + margin: 0 3px; + cursor: pointer; + + &:hover { + background: lighten($ui-base-color, 8%); + } + + &.active { + cursor: default; + background: $ui-highlight-color; + } + } + + &__action { + padding: 25px; + padding-top: 0; + display: flex; + align-items: center; + justify-content: center; + } +} diff --git a/app/javascript/styles/mastodon/widgets.scss b/app/javascript/styles/mastodon/widgets.scss index c863e3b4f..87e633c70 100644 --- a/app/javascript/styles/mastodon/widgets.scss +++ b/app/javascript/styles/mastodon/widgets.scss @@ -229,18 +229,6 @@ margin-bottom: 10px; } -.moved-account-widget, -.memoriam-widget, -.box-widget, -.contact-widget, -.landing-page__information.contact-widget { - @media screen and (max-width: $no-gap-breakpoint) { - margin-bottom: 0; - box-shadow: none; - border-radius: 0; - } -} - .page-header { background: lighten($ui-base-color, 8%); box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); @@ -261,11 +249,20 @@ font-size: 15px; color: $darker-text-color; } + + @media screen and (max-width: $no-gap-breakpoint) { + margin-top: 0; + background: lighten($ui-base-color, 4%); + + h1 { + font-size: 24px; + } + } } .directory { background: $ui-base-color; - border-radius: 0 0 4px 4px; + border-radius: 4px; box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); &__tag { @@ -407,4 +404,24 @@ font-size: 14px; } } + + @media screen and (max-width: $no-gap-breakpoint) { + tbody td.optional { + display: none; + } + } +} + +.moved-account-widget, +.memoriam-widget, +.box-widget, +.contact-widget, +.landing-page__information.contact-widget, +.directory, +.page-header { + @media screen and (max-width: $no-gap-breakpoint) { + margin-bottom: 0; + box-shadow: none; + border-radius: 0; + } } diff --git a/app/models/account.rb b/app/models/account.rb index 71264bc9f..60ad0788e 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -95,10 +95,10 @@ class Account < ApplicationRecord scope :matches_display_name, ->(value) { where(arel_table[:display_name].matches("#{value}%")) } scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) } scope :searchable, -> { where(suspended: false).where(moved_to_account_id: nil) } - scope :discoverable, -> { where(silenced: false).where(discoverable: true).joins(:account_stat).where(AccountStat.arel_table[:followers_count].gteq(MIN_FOLLOWERS_DISCOVERY)) } + scope :discoverable, -> { searchable.where(silenced: false).where(discoverable: true).joins(:account_stat).where(AccountStat.arel_table[:followers_count].gteq(MIN_FOLLOWERS_DISCOVERY)).by_recent_status } scope :tagged_with, ->(tag) { joins(:accounts_tags).where(accounts_tags: { tag_id: tag }) } - scope :popular, -> { order('account_stats.followers_count desc') } scope :by_recent_status, -> { order(Arel.sql('(case when account_stats.last_status_at is null then 1 else 0 end) asc, account_stats.last_status_at desc')) } + scope :popular, -> { order('account_stats.followers_count desc') } delegate :email, :unconfirmed_email, diff --git a/app/models/web/push_subscription.rb b/app/models/web/push_subscription.rb index d19b20c48..b57807d1c 100644 --- a/app/models/web/push_subscription.rb +++ b/app/models/web/push_subscription.rb @@ -68,6 +68,9 @@ class Web::PushSubscription < ApplicationRecord p256dh: key_p256dh, auth: key_auth, ttl: ttl, + ssl_timeout: 10, + open_timeout: 10, + read_timeout: 10, vapid: { subject: "mailto:#{::Setting.site_contact_email}", private_key: Rails.configuration.x.vapid_private_key, diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb index 38c578de2..7979c312e 100644 --- a/app/services/fetch_link_card_service.rb +++ b/app/services/fetch_link_card_service.rb @@ -137,7 +137,8 @@ class FetchLinkCardService < BaseService detector.strip_tags = true guess = detector.detect(@html, @html_charset) - page = Nokogiri::HTML(@html, nil, guess&.fetch(:encoding, nil)) + encoding = guess&.fetch(:confidence, 0).to_i > 60 ? guess&.fetch(:encoding, nil) : nil + page = Nokogiri::HTML(@html, nil, encoding) player_url = meta_property(page, 'twitter:player') if player_url && !bad_url?(Addressable::URI.parse(player_url)) diff --git a/app/views/directories/index.html.haml b/app/views/directories/index.html.haml index f70eb964a..88706def7 100644 --- a/app/views/directories/index.html.haml +++ b/app/views/directories/index.html.haml @@ -16,10 +16,6 @@ .grid .column-0 - .account__section-headline - = active_link_to t('directories.most_recently_active'), @tag ? explore_hashtag_path(@tag) : explore_path - = active_link_to t('directories.most_popular'), @tag ? explore_hashtag_popular_path(@tag) : explore_popular_path - - if @accounts.empty? = nothing_here - else @@ -29,10 +25,10 @@ - @accounts.each do |account| %tr %td= account_link_to account - %td.accounts-table__count + %td.accounts-table__count.optional = number_to_human account.statuses_count, strip_insignificant_zeros: true %small= t('accounts.posts', count: account.statuses_count).downcase - %td.accounts-table__count + %td.accounts-table__count.optional = number_to_human account.followers_count, strip_insignificant_zeros: true %small= t('accounts.followers', count: account.followers_count).downcase %td.accounts-table__count diff --git a/app/views/layouts/public.html.haml b/app/views/layouts/public.html.haml index 5545df54c..c1c0f4b87 100644 --- a/app/views/layouts/public.html.haml +++ b/app/views/layouts/public.html.haml @@ -7,9 +7,9 @@ = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon' - if Setting.profile_directory - = link_to t('directories.directory'), explore_path, class: 'nav-link' - = link_to t('about.about_this'), about_more_path, class: 'nav-link' - = link_to t('about.apps'), 'https://joinmastodon.org/apps', class: 'nav-link' + = link_to t('directories.directory'), explore_path, class: 'nav-link optional' + = link_to t('about.about_this'), about_more_path, class: 'nav-link optional' + = link_to t('about.apps'), 'https://joinmastodon.org/apps', class: 'nav-link optional' .nav-center .nav-right - if user_signed_in? diff --git a/app/workers/web/push_notification_worker.rb b/app/workers/web/push_notification_worker.rb index 4a40e5c8b..8e8a35973 100644 --- a/app/workers/web/push_notification_worker.rb +++ b/app/workers/web/push_notification_worker.rb @@ -10,8 +10,8 @@ class Web::PushNotificationWorker notification = Notification.find(notification_id) subscription.push(notification) unless notification.activity.nil? - rescue Webpush::InvalidSubscription, Webpush::ExpiredSubscription - subscription.destroy! + rescue Webpush::ResponseError => e + subscription.destroy! if (400..499).cover?(e.response.code.to_i) rescue ActiveRecord::RecordNotFound true end diff --git a/config/locales/ar.yml b/config/locales/ar.yml index eda99e24c..4de1e4e26 100644 --- a/config/locales/ar.yml +++ b/config/locales/ar.yml @@ -541,7 +541,6 @@ ar: warning_title: توافر المحتوى المنشور و المبعثَر directories: explore_mastodon: استكشف %{title} - most_popular: المشهورة errors: '403': ليس لك الصلاحيات الكافية لعرض هذه الصفحة. '404': إنّ الصفحة التي تبحث عنها لا وجود لها أصلا. diff --git a/config/locales/co.yml b/config/locales/co.yml index d2dcef9a4..80d2decd3 100644 --- a/config/locales/co.yml +++ b/config/locales/co.yml @@ -531,8 +531,6 @@ co: directory: Annuariu di i prufili explanation: Scopre utilizatori à partesi di i so centri d'interessu explore_mastodon: Scopre à %{title} - most_popular: I più pupulari - most_recently_active: Attività a più fresca people: one: "%{count} persona" other: "%{count} persone" diff --git a/config/locales/cs.yml b/config/locales/cs.yml index a5a3c0184..1bba55f0f 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -536,8 +536,6 @@ cs: directory: Adresář profilů explanation: Objevujte uživatele podle jejich zájmů explore_mastodon: Prozkoumejte %{title} - most_popular: Nejpopulárnější - most_recently_active: Naposledy aktivní people: few: "%{count} lidé" one: "%{count} člověk" diff --git a/config/locales/el.yml b/config/locales/el.yml index 342cad91c..9d41f353f 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -531,8 +531,6 @@ el: directory: Κατάλογος λογαριασμών explanation: Βρες χρήστες βάσει των ενδιαφερόντων τους explore_mastodon: Εξερεύνησε %{title} - most_popular: Δημοφιλείς - most_recently_active: Πρόσφατα ενεργοί people: one: "%{count} άτομο" other: "%{count} άτομα" diff --git a/config/locales/en.yml b/config/locales/en.yml index ca139a35e..d6f071c78 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -535,8 +535,6 @@ en: directory: Profile directory explanation: Discover users based on their interests explore_mastodon: Explore %{title} - most_popular: Most popular - most_recently_active: Most recently active people: one: "%{count} person" other: "%{count} people" diff --git a/config/locales/eu.yml b/config/locales/eu.yml index 15307c76e..c96438bc3 100644 --- a/config/locales/eu.yml +++ b/config/locales/eu.yml @@ -531,8 +531,6 @@ eu: directory: Profilen direktorioa explanation: Deskubritu erabiltzaileak interesen arabera explore_mastodon: Esploratu %{title} - most_popular: Puri-purian - most_recently_active: Azkenaldian aktibo people: one: pertsona %{count} other: "%{count} pertsona" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index de3070e8a..c171d9342 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -531,8 +531,6 @@ fr: directory: Annuaire des profils explanation: Découvrir des utilisateurs en se basant sur leurs centres d'intérêt explore_mastodon: Explorer %{title} - most_popular: Les plus populaires - most_recently_active: Les actifs les plus récents people: one: "%{count} personne" other: "%{count} personne" diff --git a/config/locales/gl.yml b/config/locales/gl.yml index 29d03859d..7c4b6e5ed 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -531,8 +531,6 @@ gl: directory: Directorio de perfil explanation: Descubra usuarias según o seu interese explore_mastodon: Explorar %{title} - most_popular: Máis popular - most_recently_active: Máis activa recentemente people: one: "%{count} persoa" other: "%{count} persoas" diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 06c8d28d7..21e4236a8 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -530,8 +530,6 @@ ja: directories: directory: ディレクトリ explore_mastodon: "%{title}を探索" - most_popular: 人気順 - most_recently_active: 直近の活動順 people: one: "%{count} 人" other: "%{count} 人" diff --git a/config/locales/nl.yml b/config/locales/nl.yml index b1a34efbd..8c654034c 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -531,8 +531,6 @@ nl: directory: Gebruikersgids explanation: Ontdek gebruikers aan de hand van hun interesses explore_mastodon: "%{title} verkennen" - most_popular: Meest populair - most_recently_active: Recentelijk actief people: one: "%{count} gebruikers" other: "%{count} gebruikers" diff --git a/config/locales/oc.yml b/config/locales/oc.yml index 9e798067f..b65e02210 100644 --- a/config/locales/oc.yml +++ b/config/locales/oc.yml @@ -587,8 +587,6 @@ oc: directory: Annuari de perfils explanation: Trobar d’utilizaires segon lor interèsses explore_mastodon: Explorar %{title} - most_popular: Mai populars - most_recently_active: Mai actius recentament people: one: "%{count} persona" other: "%{count} personas" diff --git a/config/locales/pl.yml b/config/locales/pl.yml index e0bf082b2..0e4498eee 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -541,8 +541,6 @@ pl: directory: Katalog profilów explanation: Poznaj profile na podstawie zainteresowań explore_mastodon: Odkrywaj %{title} - most_popular: Napopularniejsi - most_recently_active: Ostatnio aktywni people: few: "%{count} osoby" many: "%{count} osób" diff --git a/config/locales/sk.yml b/config/locales/sk.yml index 135fee51f..25d157155 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -536,8 +536,6 @@ sk: directory: Databáza profilov explanation: Pátraj po užívateľoch podľa ich záujmov explore_mastodon: Prebádaj %{title} - most_popular: Najpopulárnejšie - most_recently_active: Naposledy aktívni people: few: "%{count} ľudia" one: "%{count} človek" diff --git a/config/routes.rb b/config/routes.rb index c0cfbf465..918c69cc2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -81,9 +81,7 @@ Rails.application.routes.draw do post '/interact/:id', to: 'remote_interaction#create' get '/explore', to: 'directories#index', as: :explore - get '/explore/popular', to: 'directories#index', as: :explore_popular get '/explore/:id', to: 'directories#show', as: :explore_hashtag - get '/explore/:id/popular', to: 'directories#show', as: :explore_hashtag_popular namespace :settings do resource :profile, only: [:show, :update] diff --git a/lib/mastodon/accounts_cli.rb b/lib/mastodon/accounts_cli.rb index 9f7870bcd..b21968223 100644 --- a/lib/mastodon/accounts_cli.rb +++ b/lib/mastodon/accounts_cli.rb @@ -309,8 +309,8 @@ module Mastodon end old_key = account.private_key - new_key = OpenSSL::PKey::RSA.new(2048).to_pem - account.update(private_key: new_key) + new_key = OpenSSL::PKey::RSA.new(2048) + account.update(private_key: new_key.to_pem, public_key: new_key.public_key.to_pem) ActivityPub::UpdateDistributionWorker.perform_in(delay, account.id, sign_with: old_key) end end diff --git a/spec/controllers/api/v1/accounts/pins_controller_spec.rb b/spec/controllers/api/v1/accounts/pins_controller_spec.rb new file mode 100644 index 000000000..c71935df2 --- /dev/null +++ b/spec/controllers/api/v1/accounts/pins_controller_spec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Api::V1::Accounts::PinsController, type: :controller do + let(:john) { Fabricate(:user, account: Fabricate(:account, username: 'john')) } + let(:kevin) { Fabricate(:user, account: Fabricate(:account, username: 'kevin')) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: john.id, scopes: 'write:accounts') } + + before do + kevin.account.followers << john.account + allow(controller).to receive(:doorkeeper_token) { token } + end + + describe 'POST #create' do + subject { post :create, params: { account_id: kevin.account.id } } + + it 'returns 200' do + expect(response).to have_http_status(200) + end + + it 'creates account_pin' do + expect do + subject + end.to change { AccountPin.where(account: john.account, target_account: kevin.account).count }.by(1) + end + end + + describe 'DELETE #destroy' do + subject { delete :destroy, params: { account_id: kevin.account.id } } + + before do + Fabricate(:account_pin, account: john.account, target_account: kevin.account) + end + + it 'returns 200' do + expect(response).to have_http_status(200) + end + + it 'destroys account_pin' do + expect do + subject + end.to change { AccountPin.where(account: john.account, target_account: kevin.account).count }.by(-1) + end + end +end diff --git a/spec/controllers/api/v1/endorsements_controller_spec.rb b/spec/controllers/api/v1/endorsements_controller_spec.rb new file mode 100644 index 000000000..ad5ff400f --- /dev/null +++ b/spec/controllers/api/v1/endorsements_controller_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Api::V1::EndorsementsController, type: :controller do + let(:user) { Fabricate(:user) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') } + + describe 'GET #index' do + it 'returns 200' do + allow(controller).to receive(:doorkeeper_token) { token } + get :index + + expect(response).to have_http_status(200) + end + end +end diff --git a/spec/controllers/api/v1/instances/activity_controller_spec.rb b/spec/controllers/api/v1/instances/activity_controller_spec.rb new file mode 100644 index 000000000..159792ee0 --- /dev/null +++ b/spec/controllers/api/v1/instances/activity_controller_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Api::V1::Instances::ActivityController, type: :controller do + describe 'GET #show' do + it 'returns 200' do + get :show + expect(response).to have_http_status(200) + end + + context '!Setting.activity_api_enabled' do + it 'returns 404' do + Setting.activity_api_enabled = false + + get :show + expect(response).to have_http_status(404) + end + end + end +end diff --git a/spec/controllers/api/v1/instances/peers_controller_spec.rb b/spec/controllers/api/v1/instances/peers_controller_spec.rb new file mode 100644 index 000000000..12a214a83 --- /dev/null +++ b/spec/controllers/api/v1/instances/peers_controller_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Api::V1::Instances::PeersController, type: :controller do + describe 'GET #index' do + it 'returns 200' do + get :index + expect(response).to have_http_status(200) + end + + context '!Setting.peers_api_enabled' do + it 'returns 404' do + Setting.peers_api_enabled = false + + get :index + expect(response).to have_http_status(404) + end + end + end +end diff --git a/spec/controllers/api/v1/timelines/direct_controller_spec.rb b/spec/controllers/api/v1/timelines/direct_controller_spec.rb new file mode 100644 index 000000000..a22c2cbea --- /dev/null +++ b/spec/controllers/api/v1/timelines/direct_controller_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Api::V1::Timelines::DirectController, type: :controller do + let(:user) { Fabricate(:user) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:statuses') } + + describe 'GET #show' do + it 'returns 200' do + allow(controller).to receive(:doorkeeper_token) { token } + get :show + + expect(response).to have_http_status(200) + end + end +end diff --git a/spec/controllers/concerns/accountable_concern_spec.rb b/spec/controllers/concerns/accountable_concern_spec.rb new file mode 100644 index 000000000..e3c06b494 --- /dev/null +++ b/spec/controllers/concerns/accountable_concern_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe AccountableConcern do + class Hoge + include AccountableConcern + attr_reader :current_account + + def initialize(current_account) + @current_account = current_account + end + end + + let(:user) { Fabricate(:user, account: Fabricate(:account)) } + let(:target) { Fabricate(:user, account: Fabricate(:account)) } + let(:hoge) { Hoge.new(user.account) } + + describe '#log_action' do + it 'creates Admin::ActionLog' do + expect do + hoge.log_action(:create, target.account) + end.to change { Admin::ActionLog.count }.by(1) + end + end +end diff --git a/spec/fixtures/requests/windows-1251.txt b/spec/fixtures/requests/windows-1251.txt new file mode 100644 index 000000000..f573e28b2 --- /dev/null +++ b/spec/fixtures/requests/windows-1251.txt @@ -0,0 +1,17 @@ +HTTP/1.1 200 OK +server: nginx +date: Wed, 12 Dec 2018 13:14:03 GMT +content-type: text/html +content-length: 190 +accept-ranges: bytes + +<!DOCTYPE html> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=windows-1251" /> + <title> </title> +</head> +<body> + <p> </p> +</body> +</html> diff --git a/spec/services/fetch_link_card_service_spec.rb b/spec/services/fetch_link_card_service_spec.rb index 88c5339db..50c60aafd 100644 --- a/spec/services/fetch_link_card_service_spec.rb +++ b/spec/services/fetch_link_card_service_spec.rb @@ -17,6 +17,8 @@ RSpec.describe FetchLinkCardService, type: :service do stub_request(:head, 'https://github.com/qbi/WannaCry').to_return(status: 404) stub_request(:head, 'http://example.com/test-').to_return(status: 200, headers: { 'Content-Type' => 'text/html' }) stub_request(:get, 'http://example.com/test-').to_return(request_fixture('idn.txt')) + stub_request(:head, 'http://example.com/windows-1251').to_return(status: 200, headers: { 'Content-Type' => 'text/html' }) + stub_request(:get, 'http://example.com/windows-1251').to_return(request_fixture('windows-1251.txt')) subject.call(status) end @@ -58,6 +60,15 @@ RSpec.describe FetchLinkCardService, type: :service do end context do + let(:status) { Fabricate(:status, text: 'Check out http://example.com/windows-1251') } + + it 'works with windows-1251' do + expect(a_request(:get, 'http://example.com/windows-1251')).to have_been_made.at_least_once + expect(status.preview_cards.first.title).to eq('сэмпл текст') + end + end + + context do let(:status) { Fabricate(:status, text: 'テストhttp://example.com/日本語') } it 'works with Japanese path string' do |