import generator , { Entity , FilterContext } from 'megalodon' ; import { Module , MutationTree , ActionTree , GetterTree } from 'vuex' ; import { RootState } from '@/store' ; import { LocalMarker } from '~/src/types/localMarker' ; import { MyWindow } from '~/src/types/global' ; import { LoadingCard } from '@/types/loading-card' ; const win = ( window as any ) as MyWindow ; export type HomeState = { lazyLoading : boolean ; heading : boolean ; showReblogs : boolean ; showReplies : boolean ; timeline : Array < Entity . Status | LoadingCard > ; scrolling : boolean ; } ; const state = ( ) : HomeState => ( { lazyLoading : false , heading : true , timeline : [ ] , showReblogs : true , showReplies : true , scrolling : false } ) ; export const MUTATION_TYPES = { CHANGE_LAZY_LOADING : 'changeLazyLoading' , CHANGE_HEADING : 'changeHeading' , APPEND_TIMELINE : 'appendTimeline' , UPDATE_TIMELINE : 'updateTimeline' , INSERT_TIMELINE : 'insertTimeline' , ARCHIVE_TIMELINE : 'archiveTimeline' , CLEAR_TIMELINE : 'clearTimeline' , UPDATE_TOOT : 'updateToot' , DELETE_TOOT : 'deleteToot' , SHOW_REBLOGS : 'showReblogs' , SHOW_REPLIES : 'showReplies' , CHANGE_SCROLLING : 'changeScrolling' , APPEND_TIMELINE_AFTER_LOADING_CARD : 'appendTimelineAfterLoadingCard' } ; const mutations = { [ MUTATION_TYPES . CHANGE_LAZY_LOADING ] : ( state , value ) => { state . lazyLoading = value ; } , [ MUTATION_TYPES . CHANGE_HEADING ] : ( state , value ) => { state . heading = value ; } , [ MUTATION_TYPES . APPEND_TIMELINE ] : ( state , update ) => { if ( ! state . timeline . find ( item => item . id === update . id ) ) { state . timeline = ( [ update ] as Array < Entity . Status | LoadingCard > ) . concat ( state . timeline ) ; } } , [ MUTATION_TYPES . UPDATE_TIMELINE ] : ( state , messages ) => { state . timeline = messages ; } , [ MUTATION_TYPES . INSERT_TIMELINE ] : ( state , messages ) => { state . timeline = state . timeline . concat ( messages ) ; } , [ MUTATION_TYPES . ARCHIVE_TIMELINE ] : state => { state . timeline = state . timeline . slice ( 0 , 40 ) ; } , [ MUTATION_TYPES . CLEAR_TIMELINE ] : state => { state . timeline = [ ] ; } , [ MUTATION_TYPES . UPDATE_TOOT ] : ( state , message ) => { state . timeline = state . timeline . map ( status => { if ( status . id === 'loading-card' ) { return status ; } const toot = status as Entity . Status ; if ( toot . id === message . id ) { return message ; } else if ( toot . reblog !== null && toot . reblog . id === message . id ) { const reblog = { reblog : message } ; return Object . assign ( toot , reblog ) ; } else { return toot ; } } ) ; } , [ MUTATION_TYPES . DELETE_TOOT ] : ( state , messageId ) => { state . timeline = state . timeline . filter ( status => { if ( status . id === 'loading-card' ) { return true ; } const toot = status as Entity . Status ; if ( toot . reblog !== null && toot . reblog . id === messageId ) { return false ; } else { return toot . id !== messageId ; } } ) ; } , [ MUTATION_TYPES . SHOW_REBLOGS ] : ( state , visible ) => { state . showReblogs = visible ; } , [ MUTATION_TYPES . SHOW_REPLIES ] : ( state , visible ) => { state . showReplies = visible ; } , [ MUTATION_TYPES . CHANGE_SCROLLING ] : ( state , value ) => { state . scrolling = value ; } , [ MUTATION_TYPES . APPEND_TIMELINE_AFTER_LOADING_CARD ] : ( state , timeline ) => { const tl = state . timeline . flatMap ( status => { if ( status . id !== 'loading-card' ) { return status ; } else { return timeline ; } } ) ; state . timeline = Array . from ( new Set ( tl ) ) ; } } ; const actions = { fetchTimeline : async ( { dispatch , commit , rootState } ) => { const client = generator ( rootState . TimelineSpace . sns , rootState . TimelineSpace . account . baseURL , rootState . TimelineSpace . account . accessToken , rootState . App . userAgent ) ; const localMarker = await dispatch ( 'getMarker' ) . catch ( err => { console . error ( err ) ; } ) ; if ( rootState . TimelineSpace . timelineSetting . useMarker . home && localMarker !== null ) { const last = await client . getStatus ( localMarker . last_read_id ) ; const lastReadStatus = last . data ; let timeline = [ lastReadStatus ] ; const card = { type : 'middle-load' , since_id : lastReadStatus . id , max_id : null , id : 'loading-card' } ; const res = await client . getHomeTimeline ( { limit : 40 , max_id : lastReadStatus . id } ) ; const nextResponse = await client . getHomeTimeline ( { limit : 1 , min_id : lastReadStatus . id } ) ; if ( nextResponse . data . length > 0 ) { timeline = ( [ card ] as Array < Entity . Status | LoadingCard > ) . concat ( timeline ) . concat ( res . data ) ; } else { timeline = timeline . concat ( res . data ) ; } commit ( MUTATION_TYPES . UPDATE_TIMELINE , timeline ) ; return res . data ; } else { const res = await client . getHomeTimeline ( { limit : 40 } ) ; commit ( MUTATION_TYPES . UPDATE_TIMELINE , res . data ) ; return res . data ; } } , lazyFetchTimeline : async ( { state , commit , rootState } , lastStatus ) : Promise < Array < Entity . Status > | null > => { if ( state . lazyLoading ) { return Promise . resolve ( null ) ; } commit ( MUTATION_TYPES . CHANGE_LAZY_LOADING , true ) ; const client = generator ( rootState . TimelineSpace . sns , rootState . TimelineSpace . account . baseURL , rootState . TimelineSpace . account . accessToken , rootState . App . userAgent ) ; return client . getHomeTimeline ( { max_id : lastStatus . id , limit : 40 } ) . then ( res => { commit ( MUTATION_TYPES . INSERT_TIMELINE , res . data ) ; return res . data ; } ) . finally ( ( ) => { commit ( MUTATION_TYPES . CHANGE_LAZY_LOADING , false ) ; } ) ; } , fetchTimelineSince : async ( { state , rootState , commit } , since_id ) : Promise < Array < Entity . Status > | null > => { const client = generator ( rootState . TimelineSpace . sns , rootState . TimelineSpace . account . baseURL , rootState . TimelineSpace . account . accessToken , rootState . App . userAgent ) ; const cardIndex = state . timeline . findIndex ( s => { if ( s . id === 'loading-card' ) { return true ; } return false ; } ) ; let maxID = null ; if ( cardIndex > 0 ) { maxID = state . timeline [ cardIndex - 1 ] . id ; } let params = { min_id : since_id , limit : 40 } ; if ( maxID !== null ) { params = Object . assign ( { } , params , { max_id : maxID } ) ; } const res = await client . getHomeTimeline ( params ) ; if ( res . data . length >= 40 ) { const card = { type : 'middle-load' , since_id : res . data [ 0 ] . id , max_id : maxID , id : 'loading-card' } ; let timeline = [ card ] ; timeline = timeline . concat ( res . data ) ; commit ( MUTATION_TYPES . APPEND_TIMELINE_AFTER_LOADING_CARD , timeline ) ; } else { commit ( MUTATION_TYPES . APPEND_TIMELINE_AFTER_LOADING_CARD , res . data ) ; } return res . data ; } , getMarker : async ( { rootState } ) : Promise < LocalMarker | null > => { if ( ! rootState . TimelineSpace . timelineSetting . useMarker . home ) { return null ; } const client = generator ( rootState . TimelineSpace . sns , rootState . TimelineSpace . account . baseURL , rootState . TimelineSpace . account . accessToken , rootState . App . userAgent ) ; let serverMarker = { } ; try { const res = await client . getMarkers ( [ 'home' ] ) ; serverMarker = res . data ; } catch ( err ) { console . warn ( err ) ; } if ( ( serverMarker as Entity . Marker ) . home !== undefined ) { return { timeline : 'home' , last_read_id : ( serverMarker as Entity . Marker ) . home . last_read_id } as LocalMarker ; } const localMarker = await win . ipcRenderer . invoke ( 'get-home-marker' , rootState . TimelineSpace . account . _id ) ; return localMarker ; } , saveMarker : async ( { state , rootState } ) => { const timeline = state . timeline ; if ( timeline . length === 0 || timeline [ 0 ] . id === 'loading-card' ) { return ; } win . ipcRenderer . send ( 'save-marker' , { owner_id : rootState . TimelineSpace . account . _id , timeline : 'home' , last_read_id : timeline [ 0 ] . id } as LocalMarker ) ; if ( rootState . TimelineSpace . sns === 'misskey' ) { return ; } const client = generator ( rootState . TimelineSpace . sns , rootState . TimelineSpace . account . baseURL , rootState . TimelineSpace . account . accessToken , rootState . App . userAgent ) ; const res = await client . saveMarkers ( { home : { last_read_id : timeline [ 0 ] . id } } ) ; return res . data ; } } ; const getters = { filters : ( _state , _getters , rootState ) => { return rootState . TimelineSpace . filters . filter ( f => f . context . includes ( FilterContext . Home ) && ! f . irreversible ) ; } } ; const Home = { namespaced : true , state : state , mutations : mutations , actions : actions , getters : getters } ; export default Home ;