import { createSelector , createSlice , nanoid , PayloadAction , } from '@reduxjs/toolkit' ; import { RGBColor } from 'react-color' ; import { AppThunk , RootState } from 'store/store' ; import convertToPlainText from 'utils/convertToPlainText' ; import { goToView } from './navigation' ; type ImageRendition = { size : { width : number ; height : number ; } ; path : string ; } ; export interface Image { small : ImageRendition ; medium : ImageRendition ; big : ImageRendition ; } export interface Video { path : string ; url : string ; } export interface Audio { path : string ; } export interface RichText { contentState : string ; } export interface Embed { id : string ; url : string ; source : 'youtube' | 'vimeo' | 'dailymotion' ; embed : string ; } type AlignBackgroundImageText = 'left' | 'right' | 'center' ; interface OffsetPortraitVideo { align : 'left' | 'right' | 'center' | 'custom' ; customValue : number ; } interface BackgroundImage { id : string ; type : 'backgroundImage' ; fullPage ? : boolean ; altText ? : string ; image ? : Image ; navigationTitle ? : string ; title ? : RichText ; subtitle ? : RichText ; alignBackgroundImageText ? : AlignBackgroundImageText ; text ? : RichText ; audio ? : Audio ; audioLoop ? : boolean ; } interface ParallaxImage { id : string ; type : 'parallaxImage' ; navigationTitle ? : string ; title ? : string ; subtitle ? : RichText ; image ? : Image ; } interface BackgroundVideoItem { id : string ; type : 'backgroundVideo' ; video ? : Video ; fullPage ? : boolean ; navigationTitle ? : string ; title ? : RichText ; subtitle ? : RichText ; text ? : RichText ; posterImage ? : Image ; backgroundTextColor ? : RGBColor ; offsetPortraitVideo ? : OffsetPortraitVideo ; } interface EmbedVideo { id : string ; navigationTitle ? : string ; type : 'embedVideo' ; link ? : string ; showControls ? : boolean ; autoAdvance ? : boolean ; embed ? : Embed ; } interface Slide { slideTitle ? : string ; image ? : Image ; slideImageAltText ? : string ; slideCredits ? : string ; } interface HorizontalSlideshow { id : string ; type : 'horizontalSlideshow' ; navigationTitle ? : string ; slideShowTitle ? : RichText ; light ? : boolean ; slides ? : Slide [ ] ; } interface VerticalSlideshow { id : string ; type : 'verticalSlideshow' ; navigationTitle ? : string ; slideShowTitle ? : RichText ; light ? : boolean ; slides ? : Slide [ ] ; } export interface TextItem { id : string ; type : 'text' ; navigationTitle ? : string ; title ? : string ; subtitle ? : RichText ; introduction ? : RichText ; light ? : boolean ; } export interface ImageAudio { id : string ; type : 'imageAudio' ; light ? : boolean ; loop ? : boolean ; image ? : Image ; fullPage ? : boolean ; altText ? : string ; imageCaption ? : string ; imageCredits ? : RichText ; audio ? : Audio ; audioCredits ? : RichText ; navigationTitle ? : string ; title ? : string ; } export interface FullpageVideo { id : string ; type : 'fullpageVideo' ; title ? : RichText ; text ? : RichText ; video ? : Video ; representativeImage ? : Image ; imageAltText ? : string ; loopVideo ? : boolean ; autoAdvance ? : boolean ; offsetPortraitVideo ? : OffsetPortraitVideo ; } export interface PaywallSeparator { id : string ; type : 'paywallSeparator' ; } export type Items = BackgroundImage | TextItem | EmbedVideo | ImageAudio | HorizontalSlideshow | VerticalSlideshow | FullpageVideo | PaywallSeparator | ParallaxImage | BackgroundVideoItem ; export type ItemTypes = Items [ 'type' ] ; export interface StoryState { metadata : { title ? : string ; description ? : string ; author ? : string ; canonicalUrl ? : string ; siteName ? : string ; siteImage ? : Image ; googleAnalyticsId ? : string ; rssPingbkack ? : string ; extraHeader ? : string ; mainFont ? : string ; headerFont ? : string ; extraCss ? : string ; } ; items : Items [ ] ; } const initialState = { metadata : { title : '' , description : '' , author : '' , canonicalUrl : '' , siteName : '' , siteImage : undefined , googleAnalyticsId : '' , rssPingbkack : '' , } , items : [ ] , } ; export const story = createSlice ( { name : 'story' , initialState , reducers : { saveForm : ( state , action ) => { return action . payload ; } , removeItem : ( state , action ) => { state . items ?. splice ( state . items . findIndex ( ( o ) => o . id === action . payload ) , 1 ) ; } , addItem : { reducer : ( state , action ) => { state . items = [ ... ( state . items || [ ] ) , { type : action . payload . type , id : action . payload . id , } , ] ; } , prepare : ( itemType ) => { return { payload : { id : nanoid ( ) , type : itemType , } , } ; } , } , setItems : ( state , action ) => { state . items = action . payload ; } , unshiftPaywallSeparator : ( state ) => { state . items ?. unshift ( { type : 'paywallSeparator' , id : nanoid ( ) , } ) ; } , removePaywallSeparator : ( state ) => { let paywallSeparatorIndex = state . items ?. findIndex ( ( i ) => i . type === 'paywallSeparator' ) ; if ( paywallSeparatorIndex !== undefined && paywallSeparatorIndex > - 1 ) { state . items ?. splice ( paywallSeparatorIndex , 1 ) ; } } , reset : ( ) => ( { ... initialState } ) , } , } ) ; export const { saveForm , removeItem , addItem , setItems , reset , unshiftPaywallSeparator , removePaywallSeparator , } = story . actions ; export const addItemAndGoToView = ( itemType ) : AppThunk => ( dispatch , getState ) => { dispatch ( addItem ( itemType ) ) ; const items = getState ( ) . story . items ; if ( items ) { const index = ( items . length || 1 ) - 1 ; const item = items [ index ] ; dispatch ( goToView ( { name : 'item' , args : { item } } ) ) ; } } ; const storySelector = ( state ) => state . story ; const allItemsSelector = ( state ) => state . story . items ; const selectedItemSelector = ( state ) => state . navigation . view ?. name === 'item' && state . navigation . view ?. args . item ; export const selectedItemIndexSelector = createSelector ( allItemsSelector , selectedItemSelector , ( allItems , selectedItem ) => { if ( selectedItem && allItems ) { return allItems . findIndex ( ( o ) => o . id === selectedItem . id ) ; } } ) ; export const enrichedStorySelector = createSelector ( storySelector , ( story ) => { return { ... story , items : story . items ?. map ( ( item ) => { let enrichedItem = { } ; Object . keys ( item ) . forEach ( ( key ) => { let value = item [ key ] ; enrichedItem [ key ] = value ; if ( 'contentState' in value ) { enrichedItem [ key ] . plainText = convertToPlainText ( value ) ; } enrichedItem [ key ] = value ; } ) ; return enrichedItem ; } ) , } ; } ) ; export default story . reducer ;