Bluesky app fork with some witchin' additions 馃挮
at main 173 lines 4.7 kB view raw
1import React from 'react' 2import { 3 type AppBskyFeedDefs, 4 type AppBskyGraphDefs, 5 AppBskyGraphStarterpack, 6} from '@atproto/api' 7import {msg, plural} from '@lingui/core/macro' 8 9import {STARTER_PACK_MAX_SIZE} from '#/lib/constants' 10import * as Toast from '#/view/com/util/Toast' 11import * as bsky from '#/types/bsky' 12 13const steps = ['Details', 'Profiles', 'Feeds'] as const 14type Step = (typeof steps)[number] 15 16type Action = 17 | {type: 'Next'} 18 | {type: 'Back'} 19 | {type: 'SetCanNext'; canNext: boolean} 20 | {type: 'SetName'; name: string} 21 | {type: 'SetDescription'; description: string} 22 | {type: 'AddProfile'; profile: bsky.profile.AnyProfileView} 23 | {type: 'RemoveProfile'; profileDid: string} 24 | {type: 'AddFeed'; feed: AppBskyFeedDefs.GeneratorView} 25 | {type: 'RemoveFeed'; feedUri: string} 26 | {type: 'SetProcessing'; processing: boolean} 27 | {type: 'SetError'; error: string} 28 29interface State { 30 canNext: boolean 31 currentStep: Step 32 name?: string 33 description?: string 34 profiles: bsky.profile.AnyProfileView[] 35 feeds: AppBskyFeedDefs.GeneratorView[] 36 processing: boolean 37 error?: string 38 transitionDirection: 'Backward' | 'Forward' 39 targetDid?: string 40} 41 42type TStateContext = [State, (action: Action) => void] 43 44const StateContext = React.createContext<TStateContext>([ 45 {} as State, 46 (_: Action) => {}, 47]) 48StateContext.displayName = 'StarterPackWizardStateContext' 49export const useWizardState = () => React.useContext(StateContext) 50 51function reducer(state: State, action: Action): State { 52 let updatedState = state 53 54 // -- Navigation 55 const currentIndex = steps.indexOf(state.currentStep) 56 if (action.type === 'Next' && state.currentStep !== 'Feeds') { 57 updatedState = { 58 ...state, 59 currentStep: steps[currentIndex + 1], 60 transitionDirection: 'Forward', 61 } 62 } else if (action.type === 'Back' && state.currentStep !== 'Details') { 63 updatedState = { 64 ...state, 65 currentStep: steps[currentIndex - 1], 66 transitionDirection: 'Backward', 67 } 68 } 69 70 switch (action.type) { 71 case 'SetName': 72 updatedState = {...state, name: action.name.slice(0, 50)} 73 break 74 case 'SetDescription': 75 updatedState = {...state, description: action.description} 76 break 77 case 'AddProfile': 78 if (state.profiles.length > STARTER_PACK_MAX_SIZE) { 79 Toast.show( 80 msg`You may only add up to ${plural(STARTER_PACK_MAX_SIZE, { 81 other: `${STARTER_PACK_MAX_SIZE} profiles`, 82 })}`.message ?? '', 83 'info', 84 ) 85 } else { 86 updatedState = {...state, profiles: [...state.profiles, action.profile]} 87 } 88 break 89 case 'RemoveProfile': 90 updatedState = { 91 ...state, 92 profiles: state.profiles.filter( 93 profile => profile.did !== action.profileDid, 94 ), 95 } 96 break 97 case 'AddFeed': 98 if (state.feeds.length >= 3) { 99 Toast.show(msg`You may only add up to 3 feeds`.message ?? '', 'info') 100 } else { 101 updatedState = {...state, feeds: [...state.feeds, action.feed]} 102 } 103 break 104 case 'RemoveFeed': 105 updatedState = { 106 ...state, 107 feeds: state.feeds.filter(f => f.uri !== action.feedUri), 108 } 109 break 110 case 'SetProcessing': 111 updatedState = {...state, processing: action.processing} 112 break 113 } 114 115 return updatedState 116} 117 118export function Provider({ 119 starterPack, 120 listItems, 121 targetProfile, 122 children, 123}: { 124 starterPack?: AppBskyGraphDefs.StarterPackView 125 listItems?: AppBskyGraphDefs.ListItemView[] 126 targetProfile: bsky.profile.AnyProfileView 127 children: React.ReactNode 128}) { 129 const createInitialState = (): State => { 130 const targetDid = targetProfile?.did 131 132 if ( 133 starterPack && 134 bsky.validate(starterPack.record, AppBskyGraphStarterpack.validateRecord) 135 ) { 136 return { 137 canNext: true, 138 currentStep: 'Details', 139 name: starterPack.record.name, 140 description: starterPack.record.description, 141 profiles: listItems?.map(i => i.subject) ?? [], 142 feeds: starterPack.feeds ?? [], 143 processing: false, 144 transitionDirection: 'Forward', 145 targetDid, 146 } 147 } 148 149 return { 150 canNext: true, 151 currentStep: 'Details', 152 profiles: [targetProfile], 153 feeds: [], 154 processing: false, 155 transitionDirection: 'Forward', 156 targetDid, 157 } 158 } 159 160 const [state, dispatch] = React.useReducer(reducer, null, createInitialState) 161 162 return ( 163 <StateContext.Provider value={[state, dispatch]}> 164 {children} 165 </StateContext.Provider> 166 ) 167} 168 169export { 170 type Action as WizardAction, 171 type State as WizardState, 172 type Step as WizardStep, 173}