new MediaAttachment video style :playable for mp4 to make videojs work with multiple files, hiding albums, hiding bookmark collections. may need tweaks on mediaattachment for mov and other formats : todo :
This commit is contained in:
mgabdev 2020-12-22 12:11:22 -05:00
parent 6fbea0a59e
commit 34f6a1ab5b
28 changed files with 259 additions and 138 deletions

View File

@ -0,0 +1,65 @@
# frozen_string_literal: true
class Api::V1::AlbumListsController < Api::BaseController
before_action :require_user!
before_action :set_account, only: [:show]
after_action :insert_pagination_headers, only: :show
def show
@albums = load_albums
render json: @albums, each_serializer: REST::AlbumSerializer
end
private
def load_albums
paginated_albums
end
def paginated_albums
@paginated_albums = MediaAttachmentAlbum.where(account: @account)
.paginate_by_max_id(
limit_param(DEFAULT_ACCOUNTS_LIMIT),
params[:max_id],
params[:since_id]
)
end
def insert_pagination_headers
set_pagination_headers(next_path, prev_path)
end
def next_path
if records_continue?
api_v1_album_list_url params[:id], pagination_params(max_id: pagination_max_id)
end
end
def prev_path
unless paginated_albums.empty?
api_v1_album_list_url params[:id], pagination_params(since_id: pagination_since_id)
end
end
def pagination_max_id
paginated_albums.last.id
end
def pagination_since_id
paginated_albums.first.id
end
def records_continue?
paginated_albums.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
def set_account
@account = Account.find(params[:id])
end
end

View File

@ -2,15 +2,11 @@
class Api::V1::AlbumsController < Api::BaseController class Api::V1::AlbumsController < Api::BaseController
before_action :require_user! before_action :require_user!
before_action :set_albums, only: :index
before_action :set_album, only: [:show, :update, :destroy] before_action :set_album, only: [:show, :update, :destroy]
def index
render json: @albums, each_serializer: REST::AlbumSerializer
end
def create def create
@album = "" #current_account.custom_filters.create!(resource_params) @album = current_account.media_attachment_albums.create!(resource_params)
render json: @album, serializer: REST::AlbumSerializer render json: @album, serializer: REST::AlbumSerializer
end end
@ -30,15 +26,11 @@ class Api::V1::AlbumsController < Api::BaseController
private private
def set_albums
@albums = "" #current_account.custom_filters
end
def set_album def set_album
@album = "" # current_account.custom_filters.find(params[:id]) @album = current_account.media_attachment_albums.find(params[:id])
end end
def resource_params def resource_params
params.permit(:title, :description, :visibility) params.permit(:title, :description)
end end
end end

View File

@ -47,7 +47,7 @@ export const fetchAlbums = (accountId) => (dispatch, getState) => {
dispatch(fetchAlbumsRequest(accountId)) dispatch(fetchAlbumsRequest(accountId))
api(getState).get(`/api/v1/albums/find_by_account/${accountId}`).then((response) => { api(getState).get(`/api/v1/album_lists/${accountId}`).then((response) => {
const next = getLinks(response).refs.find(link => link.rel === 'next') const next = getLinks(response).refs.find(link => link.rel === 'next')
dispatch(fetchAlbumsSuccess(response.data, accountId, next ? next.uri : null)) dispatch(fetchAlbumsSuccess(response.data, accountId, next ? next.uri : null))
}).catch((error) => { }).catch((error) => {
@ -119,7 +119,7 @@ const expandAlbumsFail = (accountId, error) => ({
/** /**
* *
*/ */
export const createAlbum = (title, description, visibility) => (dispatch, getState) => { export const createAlbum = (title, description) => (dispatch, getState) => {
if (!me || !title) return if (!me || !title) return
dispatch(createAlbumRequest()) dispatch(createAlbumRequest())
@ -127,7 +127,6 @@ export const createAlbum = (title, description, visibility) => (dispatch, getSta
api(getState).post('/api/v1/albums', { api(getState).post('/api/v1/albums', {
title, title,
description, description,
visibility,
}).then((response) => { }).then((response) => {
dispatch(createAlbumSuccess(response.data)) dispatch(createAlbumSuccess(response.data))
}).catch((error) => { }).catch((error) => {

View File

@ -21,13 +21,17 @@ class Album extends React.PureComponent {
render() { render() {
const { const {
account,
album, album,
isAddable, isAddable,
} = this.props } = this.props
const title = isAddable ? 'New album' : 'Album title' if (!isAddable && (!album || !account)) return null
const subtitle = isAddable ? '' : '10 Items'
const to = isAddable ? undefined : `/photos` const title = isAddable ? 'New album' : album.get('title')
const subtitle = isAddable ? '' : `${album.get('count')} Items`
const to = isAddable ? undefined : `/${account.get('username')}/albums/${album.get('id')}`
const albumImageUrl = !!album ? album.getIn(['cover', 'preview_url'], null) : null
return ( return (
<div className={[_s.d, _s.minW162PX, _s.px5, _s.flex1].join(' ')}> <div className={[_s.d, _s.minW162PX, _s.px5, _s.flex1].join(' ')}>
@ -40,8 +44,17 @@ class Album extends React.PureComponent {
<div className={[_s.d, _s.w100PC, _s.mt5, _s.mb10].join(' ')}> <div className={[_s.d, _s.w100PC, _s.mt5, _s.mb10].join(' ')}>
<div className={[_s.d, _s.w100PC, _s.pt100PC].join(' ')}> <div className={[_s.d, _s.w100PC, _s.pt100PC].join(' ')}>
<div className={[_s.d, _s.posAbs, _s.top0, _s.w100PC, _s.right0, _s.bottom0, _s.left0].join(' ')}> <div className={[_s.d, _s.posAbs, _s.top0, _s.w100PC, _s.right0, _s.bottom0, _s.left0].join(' ')}>
<div className={[_s.d, _s.w100PC, _s.h100PC, _s.aiCenter, _s.jcCenter, _s.radiusSmall, _s.bgTertiary, _s.border1PX, _s.borderColorSecondary].join(' ')}> <div className={[_s.d, _s.w100PC, _s.h100PC, _s.aiCenter, _s.jcCenter, _s.radiusSmall, _s.overflowHidden, _s.bgTertiary, _s.border1PX, _s.borderColorSecondary].join(' ')}>
{ isAddable && <Icon id='add' size='20px' /> } { isAddable && <Icon id='add' size='20px' /> }
{
albumImageUrl &&
<Image
height='100%'
width='100%'
className={_s.z3}
src={albumImageUrl}
/>
}
</div> </div>
</div> </div>
</div> </div>
@ -58,14 +71,19 @@ class Album extends React.PureComponent {
} }
Album.propTypes = { Album.propTypes = {
account: ImmutablePropTypes.map,
album: ImmutablePropTypes.map, album: ImmutablePropTypes.map,
isAddable: PropTypes.bool, isAddable: PropTypes.bool,
} }
const mapStateToProps = (state, { albumId }) => ({
album: state.getIn(['albums', albumId]),
})
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
openAlbumCreate() { openAlbumCreate() {
dispatch(openModal(MODAL_ALBUM_CREATE)) dispatch(openModal(MODAL_ALBUM_CREATE))
} }
}) })
export default connect(null, mapDispatchToProps)(Album) export default connect(mapStateToProps, mapDispatchToProps)(Album)

View File

@ -23,9 +23,6 @@ class ProfileNavigationBar extends React.PureComponent {
render() { render() {
const { titleHTML } = this.props const { titleHTML } = this.props
// : todo :
// fix padding on mobile device
return ( return (
<div className={[_s.d, _s.z4, _s.minH53PX, _s.w100PC].join(' ')}> <div className={[_s.d, _s.z4, _s.minH53PX, _s.w100PC].join(' ')}>

View File

@ -55,7 +55,7 @@ class MediaGalleryPanel extends ImmutablePureComponent {
noPadding noPadding
title={intl.formatMessage(messages.title)} title={intl.formatMessage(messages.title)}
headerButtonTitle={!!account ? intl.formatMessage(messages.show_all) : undefined} headerButtonTitle={!!account ? intl.formatMessage(messages.show_all) : undefined}
headerButtonTo={!!account ? `/${account.get('acct')}/albums` : undefined} headerButtonTo={!!account ? `/${account.get('acct')}/photos` : undefined}
> >
<div className={[_s.d, _s.flexRow, _s.flexWrap, _s.aiCenter, _s.jcCenter].join(' ')}> <div className={[_s.d, _s.flexRow, _s.flexWrap, _s.aiCenter, _s.jcCenter].join(' ')}>
{ {

View File

@ -19,7 +19,7 @@ import {
import { import {
muteAccount, muteAccount,
unmuteAccount, unmuteAccount,
} from '../../actions/interactions'; } from '../../actions/accounts';
import { import {
deleteStatus, deleteStatus,
editStatus, editStatus,
@ -197,11 +197,12 @@ class StatusOptionsPopover extends ImmutablePureComponent {
}) })
if (status.get('bookmarked')) { if (status.get('bookmarked')) {
menu.push({ // : todo :
icon: 'bookmark', // menu.push({
title: 'Update bookmark collection', // icon: 'bookmark',
onClick: this.handleBookmarkChangeClick, // title: 'Update bookmark collection',
}) // onClick: this.handleBookmarkChangeClick,
// })
} }
if (status.getIn(['account', 'id']) === me) { if (status.getIn(['account', 'id']) === me) {

View File

@ -121,7 +121,7 @@ class ProfileHeader extends ImmutablePureComponent {
title: intl.formatMessage(messages.comments), title: intl.formatMessage(messages.comments),
}, },
{ {
to: `/${account.get('acct')}/albums`, to: `/${account.get('acct')}/photos`,
title: intl.formatMessage(messages.photos), title: intl.formatMessage(messages.photos),
}, },
{ {
@ -137,7 +137,7 @@ class ProfileHeader extends ImmutablePureComponent {
title: 'Likes', title: 'Likes',
}) })
tabs.push({ tabs.push({
to: `/${account.get('acct')}/bookmark_collections`, to: `/${account.get('acct')}/bookmarks`,
title: intl.formatMessage(messages.bookmarks), title: intl.formatMessage(messages.bookmarks),
}) })
} }

View File

@ -32,6 +32,7 @@ class StatusCheckBox extends ImmutablePureComponent {
preview={video.get('preview_url')} preview={video.get('preview_url')}
blurhash={video.get('blurhash')} blurhash={video.get('blurhash')}
src={video.get('url')} src={video.get('url')}
sourceMp4={video.get('source_mp4')}
alt={video.get('description')} alt={video.get('description')}
aspectRatio={video.getIn(['meta', 'small', 'aspect'])} aspectRatio={video.getIn(['meta', 'small', 'aspect'])}
fileContentType={video.get('file_content_type')} fileContentType={video.get('file_content_type')}

View File

@ -65,6 +65,7 @@ class StatusMedia extends ImmutablePureComponent {
preview={video.get('preview_url')} preview={video.get('preview_url')}
blurhash={video.get('blurhash')} blurhash={video.get('blurhash')}
src={video.get('url')} src={video.get('url')}
sourceMp4={video.get('source_mp4')}
alt={video.get('description')} alt={video.get('description')}
aspectRatio={video.getIn(['meta', 'small', 'aspect'])} aspectRatio={video.getIn(['meta', 'small', 'aspect'])}
fileContentType={video.get('file_content_type')} fileContentType={video.get('file_content_type')}

View File

@ -31,10 +31,16 @@ class Video extends ImmutablePureComponent {
} }
componentDidMount() { componentDidMount() {
videoJsOptions.sources = [{ videoJsOptions.sources = [
src: this.props.src, {
type: this.props.fileContentType, src: this.props.src,
}] type: this.props.fileContentType,
},
{
src: this.props.sourceMp4,
type: 'video/mp4',
},
]
this.videoPlayer = videojs(this.video, videoJsOptions) this.videoPlayer = videojs(this.video, videoJsOptions)
} }
@ -182,6 +188,7 @@ const messages = defineMessages({
Video.propTypes = { Video.propTypes = {
preview: PropTypes.string, preview: PropTypes.string,
src: PropTypes.string.isRequired, src: PropTypes.string.isRequired,
sourceMp4: PropTypes.string,
alt: PropTypes.string, alt: PropTypes.string,
width: PropTypes.number, width: PropTypes.number,
height: PropTypes.number, height: PropTypes.number,

View File

@ -5,8 +5,8 @@ import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component' import ImmutablePureComponent from 'react-immutable-pure-component'
import { me } from '../initial_state' import { me } from '../initial_state'
import { import {
fetchAccountAlbums, fetchAlbums,
expandAccountAlbums, expandAlbums,
} from '../actions/albums' } from '../actions/albums'
import ColumnIndicator from '../components/column_indicator' import ColumnIndicator from '../components/column_indicator'
import Heading from '../components/heading' import Heading from '../components/heading'
@ -48,7 +48,7 @@ class AccountAlbums extends ImmutablePureComponent {
render() { render() {
const { const {
isMe, isMe,
albums, albumIds,
account, account,
accountId, accountId,
hasMore, hasMore,
@ -56,7 +56,9 @@ class AccountAlbums extends ImmutablePureComponent {
} = this.props } = this.props
if (!account) return null if (!account) return null
const hasAlbums = !!albums ? albums.size > 0 : false const hasAlbums = !!albumIds ? albumIds.size > 0 : false
console.log("albumIds:", albumIds)
return ( return (
<Block> <Block>
@ -69,11 +71,11 @@ class AccountAlbums extends ImmutablePureComponent {
title: 'All Photos', title: 'All Photos',
to: `/${account.get('username')}/photos`, to: `/${account.get('username')}/photos`,
}, },
{ // {
title: 'Albums', // title: 'Albums',
isActive: true, // isActive: true,
to: `/${account.get('username')}/albums`, // to: `/${account.get('username')}/albums`,
}, // },
]}/> ]}/>
</div> </div>
@ -82,10 +84,10 @@ class AccountAlbums extends ImmutablePureComponent {
{ {
hasAlbums && hasAlbums &&
albums.map((albums, i) => ( albumIds.map((albumId, i) => (
<Album <Album
key={album.get('id')} key={albumId}
album={album} albumId={albumId}
account={account} account={account}
/> />
)) ))
@ -119,7 +121,7 @@ const mapStateToProps = (state, { account, mediaType }) => {
return { return {
accountId, accountId,
albums: state.getIn(['album_lists', accountId, 'items']), albumIds: state.getIn(['album_lists', accountId, 'items']),
isLoading: state.getIn(['album_lists', accountId, 'isLoading'], false), isLoading: state.getIn(['album_lists', accountId, 'isLoading'], false),
hasMore: state.getIn(['album_lists', accountId, 'hasMore'], false), hasMore: state.getIn(['album_lists', accountId, 'hasMore'], false),
} }
@ -127,17 +129,17 @@ const mapStateToProps = (state, { account, mediaType }) => {
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
onFetchAccountAlbums(accountId) { onFetchAccountAlbums(accountId) {
dispatch(fetchAlbums(accountId))
}, },
onExpandAccountAlbums(accountId) { onExpandAccountAlbums(accountId) {
dispatch(expandAlbums(accountId))
}, },
}) })
AccountAlbums.propTypes = { AccountAlbums.propTypes = {
account: ImmutablePropTypes.map, account: ImmutablePropTypes.map,
accountId: PropTypes.string, accountId: PropTypes.string,
albums: ImmutablePropTypes.list, albumIds: ImmutablePropTypes.list,
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
hasMore: PropTypes.bool, hasMore: PropTypes.bool,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,

View File

@ -74,7 +74,6 @@ class AccountPhotoGallery extends ImmutablePureComponent {
if (!account) return null if (!account) return null
const hasAttachments = !!attachments ? attachments.size > 0 : false const hasAttachments = !!attachments ? attachments.size > 0 : false
console.log("account, isLoading, attachments:", account, isLoading, attachments, hasAttachments)
return ( return (
<Block> <Block>
@ -87,11 +86,11 @@ class AccountPhotoGallery extends ImmutablePureComponent {
title: 'All Photos', title: 'All Photos',
to: `/${account.get('username')}/photos`, to: `/${account.get('username')}/photos`,
}, },
{ // {
title: 'Albums', // title: 'Albums',
isActive: true, // isActive: true,
to: `/${account.get('username')}/albums`, // to: `/${account.get('username')}/albums`,
}, // },
]}/> ]}/>
</div> </div>

View File

@ -8,35 +8,60 @@ import Button from '../components/button'
import Input from '../components/input' import Input from '../components/input'
import Form from '../components/form' import Form from '../components/form'
import Text from '../components/text' import Text from '../components/text'
import Divider from '../components/divider'
import Textarea from '../components/textarea'
class AlbumCreate extends React.PureComponent { class AlbumCreate extends React.PureComponent {
state = { state = {
value: '', titleValue: '',
descriptionValue: '',
checked: false,
} }
onChange = (value) => { onChangeTitle = (titleValue) => {
this.setState({ value }) this.setState({ titleValue })
}
onChangeDescription = (descriptionValue) => {
this.setState({ descriptionValue })
} }
handleOnSubmit = () => { handleOnSubmit = () => {
this.props.onSubmit(this.state.value) const { titleValue, descriptionValue, checked } = this.state
this.props.onSubmit(titleValue, descriptionValue, checked)
}
onTogglePrivacy = (checked) => {
this.setState({ checked })
} }
render() { render() {
const { value } = this.state const {
titleValue,
descriptionValue,
} = this.state
const isDisabled = !value const isDisabled = !titleValue
console.log("HELLO")
return ( return (
<Form> <Form>
<Input <Input
title='Title' title='Title'
placeholder='Album title' placeholder='Album title'
value={value} value={titleValue}
onChange={this.onChange} onChange={this.onChangeTitle}
/> />
<Divider isInvisible />
<Textarea
title='Description'
placeholder='Album description'
value={descriptionValue}
onChange={this.onChangeDescription}
/>
<Divider isInvisible />
<Button <Button
isDisabled={isDisabled} isDisabled={isDisabled}
onClick={this.handleOnSubmit} onClick={this.handleOnSubmit}
@ -53,9 +78,9 @@ class AlbumCreate extends React.PureComponent {
} }
const mapDispatchToProps = (dispatch, { isModal }) => ({ const mapDispatchToProps = (dispatch, { isModal }) => ({
onSubmit(title) { onSubmit(title, description, isPrivate) {
if (isModal) dispatch(closeModal()) if (isModal) dispatch(closeModal())
dispatch(createBookmarkCollection(title)) dispatch(createAlbum(title, description, isPrivate))
}, },
}) })

View File

@ -28,7 +28,7 @@ class ChatMessagesComposeForm extends React.PureComponent {
handleOnSendChatMessage = () => { handleOnSendChatMessage = () => {
this.props.onSendChatMessage(this.state.value, this.props.chatConversationId) this.props.onSendChatMessage(this.state.value, this.props.chatConversationId)
// document.querySelector('#gabsocial').focus() // document.querySelector('#gabsocial').focus()
// this.onBlur() this.onFocus()
this.setState({ value: '' }) this.setState({ value: '' })
} }

View File

@ -53,6 +53,7 @@ import DeckPage from '../../pages/deck_page'
import { import {
About, About,
AccountAlbums, AccountAlbums,
AccountAlbumGallery,
AccountPhotoGallery, AccountPhotoGallery,
AccountVideoGallery, AccountVideoGallery,
AccountTimeline, AccountTimeline,
@ -279,18 +280,18 @@ class SwitchingArea extends React.PureComponent {
<WrappedRoute path='/:username/following' page={ProfilePage} component={Following} content={children} /> <WrappedRoute path='/:username/following' page={ProfilePage} component={Following} content={children} />
<WrappedRoute path='/:username/photos' exact page={ProfilePage} component={AccountPhotoGallery} content={children} componentParams={{ noSidebar: true }} /> <WrappedRoute path='/:username/photos' exact page={ProfilePage} component={AccountPhotoGallery} content={children} componentParams={{ noSidebar: true }} />
{ /* <WrappedRoute path='/:username/albums/:albumId' page={ProfilePage} component={AccountGallery} content={children} componentParams={{ noSidebar: true }} /> */ } { /* <WrappedRoute path='/:username/albums/:albumId' page={ProfilePage} component={AccountAlbumGallery} content={children} componentParams={{ noSidebar: true }} /> */ }
<WrappedRoute path='/:username/videos' exact page={ProfilePage} component={AccountVideoGallery} content={children} componentParams={{ noSidebar: true }} /> <WrappedRoute path='/:username/videos' exact page={ProfilePage} component={AccountVideoGallery} content={children} componentParams={{ noSidebar: true }} />
<WrappedRoute path='/:username/albums' exact page={ProfilePage} component={AccountAlbums} content={children} componentParams={{ noSidebar: true }} /> { /* <WrappedRoute path='/:username/albums' exact page={ProfilePage} component={AccountAlbums} content={children} componentParams={{ noSidebar: true }} /> */ }
{ /* <WrappedRoute path='/:username/albums/create' exact page={ModalPage} component={AlbumCreate} content={children} componentParams={{ title: 'Create Album', page: 'create-album' }} /> */ } { /* <WrappedRoute path='/:username/albums/create' exact page={ModalPage} component={AlbumCreate} content={children} componentParams={{ title: 'Create Album', page: 'create-album' }} /> */ }
{ /* <WrappedRoute path='/:username/albums/:albumId/edit' page={ModalPage} component={AlbumCreate} content={children} componentParams={{ title: 'Edit Album', page: 'edit-album' }} /> */ } { /* <WrappedRoute path='/:username/albums/:albumId/edit' page={ModalPage} component={AlbumCreate} content={children} componentParams={{ title: 'Edit Album', page: 'edit-album' }} /> */ }
<WrappedRoute path='/:username/likes' page={ProfilePage} component={LikedStatuses} content={children} /> <WrappedRoute path='/:username/likes' page={ProfilePage} component={LikedStatuses} content={children} />
<WrappedRoute path='/:username/bookmark_collections/create' page={ModalPage} component={BookmarkCollectionCreate} content={children} componentParams={{ title: 'Create Bookmark Collection', page: 'create-bookmark-collection' }} /> <WrappedRoute path='/:username/bookmarks' page={ProfilePage} component={BookmarkedStatuses} content={children} />
{/*<WrappedRoute path='/:username/bookmark_collections/create' page={ModalPage} component={BookmarkCollectionCreate} content={children} componentParams={{ title: 'Create Bookmark Collection', page: 'create-bookmark-collection' }} />
<WrappedRoute path='/:username/bookmark_collections/:bookmarkCollectionId' page={ProfilePage} component={BookmarkedStatuses} content={children} /> <WrappedRoute path='/:username/bookmark_collections/:bookmarkCollectionId' page={ProfilePage} component={BookmarkedStatuses} content={children} />
<WrappedRoute path='/:username/bookmark_collections/:bookmarkCollectionId/edit' page={ModalPage} component={BookmarkCollectionCreate} content={children} componentParams={{ title: 'Edit Bookmark Collection', page: 'edit-bookmark-collection' }} /> <WrappedRoute path='/:username/bookmark_collections/:bookmarkCollectionId/edit' page={ModalPage} component={BookmarkCollectionCreate} content={children} componentParams={{ title: 'Edit Bookmark Collection', page: 'edit-bookmark-collection' }} />
<WrappedRoute path='/:username/bookmark_collections' page={ProfilePage} component={BookmarkCollections} content={children} /> <WrappedRoute path='/:username/bookmark_collections' page={ProfilePage} component={BookmarkCollections} content={children} />*/}
<WrappedRoute path='/:username/posts/:statusId' publicRoute exact page={BasicPage} component={StatusFeature} content={children} componentParams={{ title: 'Status', page: 'status' }} /> <WrappedRoute path='/:username/posts/:statusId' publicRoute exact page={BasicPage} component={StatusFeature} content={children} componentParams={{ title: 'Status', page: 'status' }} />

View File

@ -47,6 +47,7 @@ export default function album_lists(state = initialState, action) {
case ALBUMS_EXPAND_REQUEST: case ALBUMS_EXPAND_REQUEST:
return state.setIn([action.accountId, 'isLoading'], true) return state.setIn([action.accountId, 'isLoading'], true)
case ALBUMS_FETCH_SUCCESS: case ALBUMS_FETCH_SUCCESS:
console.log("ALBUMS_FETCH_SUCCESS:", action)
return normalizeList(state, action.accountId, action.albums, action.next) return normalizeList(state, action.accountId, action.albums, action.next)
case ALBUMS_EXPAND_SUCCESS: case ALBUMS_EXPAND_SUCCESS:
return appendToList(state, action.accountId, action.albums, action.next) return appendToList(state, action.accountId, action.albums, action.next)

View File

@ -2,45 +2,26 @@ import {
ALBUMS_FETCH_REQUEST, ALBUMS_FETCH_REQUEST,
ALBUMS_FETCH_SUCCESS, ALBUMS_FETCH_SUCCESS,
ALBUMS_FETCH_FAIL, ALBUMS_FETCH_FAIL,
ALBUMS_CREATE_SUCCESS, ALBUM_CREATE_SUCCESS,
ALBUMS_REMOVE_REQUEST, ALBUM_REMOVE_REQUEST,
} from '../actions/albums' } from '../actions/albums'
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable' import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'
const initialState = ImmutableMap({ const importAlbum = (state, album) => state.set(album.id, fromJS(album))
items: ImmutableList(),
isLoading: false, const importAlbums = (state, albums) =>
isFetched: false, state.withMutations((mutable) => albums.forEach((album) => importAlbum(mutable, album)))
isError: false,
}) const initialState = ImmutableMap()
export default function albums(state = initialState, action) { export default function albums(state = initialState, action) {
switch(action.type) { switch(action.type) {
case ALBUMS_FETCH_REQUEST:
return state.withMutations((map) => {
map.set('isLoading', true)
map.set('isFetched', false)
map.set('isError', false)
})
case ALBUMS_FETCH_SUCCESS: case ALBUMS_FETCH_SUCCESS:
return state.withMutations((map) => { return importAlbums(state, action.albums)
map.set('items', fromJS(action.albums)) case ALBUM_CREATE_SUCCESS:
map.set('isLoading', false) return importAlbum(state, action.album)
map.set('isFetched', true) case ALBUM_REMOVE_REQUEST:
map.set('isError', false) return state.delete(action.albumId)
})
case ALBUMS_FETCH_FAIL:
return state.withMutations((map) => {
map.set('isLoading', false)
map.set('isFetched', true)
map.set('isError', true)
})
case ALBUMS_CREATE_SUCCESS:
return state.update('items', list => list.push(fromJS(action.albums)))
case ALBUMS_REMOVE_REQUEST:
return state.update('items', list => list.filterNot((item) => {
return item.get('id') === action.albumId
}))
default: default:
return state return state
} }

View File

@ -57,8 +57,8 @@ import user_lists from './user_lists'
const reducers = { const reducers = {
accounts, accounts,
accounts_counters, accounts_counters,
// albums, albums,
// album_lists, album_lists,
bookmark_collections, bookmark_collections,
chats, chats,
chat_conversation_lists, chat_conversation_lists,

View File

@ -39,13 +39,6 @@ class SortingQueryBuilder < BaseService
query = query.where('statuses.id > ? AND statuses.id <> ?', max_id, max_id) unless max_id.nil? || max_id.empty? query = query.where('statuses.id > ? AND statuses.id <> ?', max_id, max_id) unless max_id.nil? || max_id.empty?
query = query.limit(20) query = query.limit(20)
# : todo : reject blocks, etc. in feedmanager
# SELECT "statuses".*
# FROM "statuses"
# INNER JOIN "status_stats" ON "status_stats"."status_id" = "statuses"."id"
# WHERE (statuses.reply IS FALSE) AND (statuses.created_at > '2020-11-02 22:01:36.197805')
# ORDER BY "statuses"."created_at" DESC, status_stats.favourites_count DESC, status_stats.reblogs_count DESC, status_stats.replies_count DESC LIMIT $1
query query
end end

View File

@ -306,11 +306,6 @@ class Account < ApplicationRecord
username username
end end
def excluded_from_chat_account_ids
# : todo :
# Rails.cache.fetch("exclude_account_ids_for:#{id}") { blocking.pluck(:target_account_id) + blocked_by.pluck(:account_id) + muting.pluck(:target_account_id) }
end
def excluded_from_timeline_account_ids def excluded_from_timeline_account_ids
Rails.cache.fetch("exclude_account_ids_for:#{id}") { blocking.pluck(:target_account_id) + blocked_by.pluck(:account_id) + muting.pluck(:target_account_id) } Rails.cache.fetch("exclude_account_ids_for:#{id}") { blocking.pluck(:target_account_id) + blocked_by.pluck(:account_id) + muting.pluck(:target_account_id) }
end end

View File

@ -20,8 +20,6 @@
# : todo : max per account # : todo : max per account
class ChatConversationAccount < ApplicationRecord class ChatConversationAccount < ApplicationRecord
include Paginable include Paginable
PER_ACCOUNT_APPROVED_LIMIT = 250
EXPIRATION_POLICY_MAP = { EXPIRATION_POLICY_MAP = {
none: nil, none: nil,

View File

@ -29,6 +29,7 @@ module AccountAssociations
# Media # Media
has_many :media_attachments, dependent: :destroy has_many :media_attachments, dependent: :destroy
has_many :media_attachment_albums, dependent: :destroy
has_many :polls, dependent: :destroy has_many :polls, dependent: :destroy
# Push subscriptions # Push subscriptions

View File

@ -28,11 +28,11 @@ class MediaAttachment < ApplicationRecord
enum type: [:image, :gifv, :video, :unknown] enum type: [:image, :gifv, :video, :unknown]
IMAGE_FILE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.webp'].freeze IMAGE_FILE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.webp'].freeze
VIDEO_FILE_EXTENSIONS = ['.webm', '.mp4', '.m4v'].freeze VIDEO_FILE_EXTENSIONS = ['.webm', '.mp4', '.m4v', '.mov'].freeze
IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].freeze IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].freeze
VIDEO_MIME_TYPES = ['video/webm', 'video/mp4'].freeze VIDEO_MIME_TYPES = ['video/webm', 'video/mp4', 'video/quicktime'].freeze
VIDEO_CONVERTIBLE_MIME_TYPES = ['video/webm'].freeze VIDEO_CONVERTIBLE_MIME_TYPES = ['video/webm', 'video/quicktime'].freeze
BLURHASH_OPTIONS = { BLURHASH_OPTIONS = {
x_comp: 4, x_comp: 4,
@ -64,6 +64,23 @@ class MediaAttachment < ApplicationRecord
file_geometry_parser: FastGeometryParser, file_geometry_parser: FastGeometryParser,
blurhash: BLURHASH_OPTIONS, blurhash: BLURHASH_OPTIONS,
}, },
playable: {
convert_options: {
output: {
'loglevel' => 'fatal',
'movflags' => 'faststart',
'pix_fmt' => 'yuv420p',
'vf' => 'scale=\'trunc(iw/2)*2:trunc(ih/2)*2\'',
'vsync' => 'cfr',
'c:v' => 'h264',
'b:v' => '500K',
'maxrate' => '1300K',
'bufsize' => '1300K',
'crf' => 18,
},
},
format: 'mp4',
},
}.freeze }.freeze
VIDEO_FORMAT = { VIDEO_FORMAT = {
@ -170,9 +187,9 @@ class MediaAttachment < ApplicationRecord
elsif IMAGE_MIME_TYPES.include? f.instance.file_content_type elsif IMAGE_MIME_TYPES.include? f.instance.file_content_type
IMAGE_STYLES IMAGE_STYLES
elsif VIDEO_CONVERTIBLE_MIME_TYPES.include?(f.instance.file_content_type) elsif VIDEO_CONVERTIBLE_MIME_TYPES.include?(f.instance.file_content_type)
puts "tilly convert"
{ {
small: VIDEO_STYLES[:small], small: VIDEO_STYLES[:small],
playable: VIDEO_STYLES[:playable],
original: VIDEO_FORMAT, original: VIDEO_FORMAT,
} }
else else

View File

@ -16,11 +16,14 @@
class MediaAttachmentAlbum < ApplicationRecord class MediaAttachmentAlbum < ApplicationRecord
include Paginable
enum visibility: [ enum visibility: [
:public, :public,
:private, :private,
], _suffix: :visibility ], _suffix: :visibility
belongs_to :account belongs_to :account
belongs_to :cover, class_name: 'MediaAttachment', optional: true
end end

View File

@ -0,0 +1,12 @@
# frozen_string_literal: true
class REST::AlbumSerializer < ActiveModel::Serializer
attributes :id, :title, :description, :count
belongs_to :cover, serializer: REST::MediaAttachmentSerializer
def id
object.id.to_s
end
end

View File

@ -3,7 +3,7 @@
class REST::MediaAttachmentSerializer < ActiveModel::Serializer class REST::MediaAttachmentSerializer < ActiveModel::Serializer
include RoutingHelper include RoutingHelper
attributes :id, :type, :url, :preview_url, attributes :id, :type, :url, :preview_url, :source_mp4,
:remote_url, :text_url, :meta, :remote_url, :text_url, :meta,
:description, :blurhash, :file_content_type :description, :blurhash, :file_content_type
@ -33,6 +33,18 @@ class REST::MediaAttachmentSerializer < ActiveModel::Serializer
end end
end end
def source_mp4
if object.type == "image" || object.type == "gifv" || object.type == "unknown"
return nil
else
if object.needs_redownload?
media_proxy_url(object.id, :playable)
else
full_asset_url(object.file.url(:playable))
end
end
end
def remote_url def remote_url
object.remote_url.presence object.remote_url.presence
end end

View File

@ -288,7 +288,8 @@ Rails.application.routes.draw do
resources :reports, only: [:create] resources :reports, only: [:create]
resources :filters, only: [:index, :create, :show, :update, :destroy] resources :filters, only: [:index, :create, :show, :update, :destroy]
resources :shortcuts, only: [:index, :create, :show, :destroy] resources :shortcuts, only: [:index, :create, :show, :destroy]
resources :albums, only: [:index, :create, :update, :show, :destroy] resources :albums, only: [:create, :update, :show, :destroy]
resources :album_lists, only: [:show]
resources :bookmark_collections, only: [:index, :create, :show, :update, :destroy] do resources :bookmark_collections, only: [:index, :create, :show, :update, :destroy] do
resources :bookmarks, only: [:index], controller: 'bookmark_collections/bookmarks' resources :bookmarks, only: [:index], controller: 'bookmark_collections/bookmarks'
@ -398,14 +399,6 @@ Rails.application.routes.draw do
end end
end end
# : todo :
# get '/:username/with_replies', to: 'accounts#show', username: username_regex, as: :short_account_with_replies
# get '/:username/comments_only', to: 'accounts#show', username: username_regex, as: :short_account_comments_only
# get '/:username/media', to: 'accounts#show', username: username_regex, as: :short_account_media
# get '/:username/tagged/:tag', to: 'accounts#show', username: username_regex, as: :short_account_tag
# get '/:username/posts/:statusId/reblogs', to: 'statuses#show', username: username_regex
# get '/:account_username/posts/:id', to: 'statuses#show', account_username: username_regex, as: :short_account_status
# get '/:account_username/posts/:id/embed', to: 'statuses#embed', account_username: username_regex, as: :embed_short_account_status
get '/g/:groupSlug', to: 'react#groupBySlug' get '/g/:groupSlug', to: 'react#groupBySlug'
get '/(*any)', to: 'react#react', as: :web get '/(*any)', to: 'react#react', as: :web
@ -414,6 +407,13 @@ Rails.application.routes.draw do
get '/', to: 'react#react', as: :homepage get '/', to: 'react#react', as: :homepage
# : todo : # : todo :
# get '/:username/with_replies', to: 'accounts#show', username: username_regex, as: :short_account_with_replies
# get '/:username/comments_only', to: 'accounts#show', username: username_regex, as: :short_account_comments_only
# get '/:username/media', to: 'accounts#show', username: username_regex, as: :short_account_media
# get '/:username/tagged/:tag', to: 'accounts#show', username: username_regex, as: :short_account_tag
# get '/:username/posts/:statusId/reblogs', to: 'statuses#show', username: username_regex
# get '/:account_username/posts/:id', to: 'statuses#show', account_username: username_regex, as: :short_account_status
# get '/:account_username/posts/:id/embed', to: 'statuses#embed', account_username: username_regex, as: :embed_short_account_status
get '/about', to: 'react#react' get '/about', to: 'react#react'
get '/about/tos', to: 'react#react' get '/about/tos', to: 'react#react'
get '/about/privacy', to: 'react#react' get '/about/privacy', to: 'react#react'