this repo has no description
at hotfix/infinite-loop-intersection-observer 247 lines 8.3 kB view raw
1import './drafts.css'; 2 3import { useEffect, useMemo, useReducer, useState } from 'react'; 4 5import { api } from '../utils/api'; 6import db from '../utils/db'; 7import niceDateTime from '../utils/nice-date-time'; 8import states from '../utils/states'; 9import { getCurrentAccountNS } from '../utils/store-utils'; 10 11import Icon from './icon'; 12import Loader from './loader'; 13import MenuConfirm from './menu-confirm'; 14 15function Drafts({ onClose }) { 16 const { masto } = api(); 17 const [uiState, setUIState] = useState('default'); 18 const [drafts, setDrafts] = useState([]); 19 const [reloadCount, reload] = useReducer((c) => c + 1, 0); 20 21 useEffect(() => { 22 setUIState('loading'); 23 (async () => { 24 try { 25 const keys = await db.drafts.keys(); 26 if (keys.length) { 27 const ns = getCurrentAccountNS(); 28 const ownKeys = keys.filter((key) => key.startsWith(ns)); 29 if (ownKeys.length) { 30 const drafts = await db.drafts.getMany(ownKeys); 31 drafts.sort( 32 (a, b) => 33 new Date(b.updatedAt).getTime() - 34 new Date(a.updatedAt).getTime(), 35 ); 36 setDrafts(drafts); 37 } else { 38 setDrafts([]); 39 } 40 } else { 41 setDrafts([]); 42 } 43 setUIState('default'); 44 } catch (e) { 45 console.error(e); 46 setUIState('error'); 47 } 48 })(); 49 }, [reloadCount]); 50 51 const hasDrafts = drafts?.length > 0; 52 53 return ( 54 <div class="sheet"> 55 {!!onClose && ( 56 <button type="button" class="sheet-close" onClick={onClose}> 57 <Icon icon="x" /> 58 </button> 59 )} 60 <header> 61 <h2> 62 Unsent drafts <Loader abrupt hidden={uiState !== 'loading'} /> 63 </h2> 64 {hasDrafts && ( 65 <div class="insignificant"> 66 Looks like you have unsent drafts. Let's continue where you left 67 off. 68 </div> 69 )} 70 </header> 71 <main> 72 {hasDrafts ? ( 73 <> 74 <ul class="drafts-list"> 75 {drafts.map((draft) => { 76 const { updatedAt, key, draftStatus, replyTo } = draft; 77 const updatedAtDate = new Date(updatedAt); 78 return ( 79 <li key={updatedAt}> 80 <div class="mini-draft-meta"> 81 <b> 82 <Icon icon={replyTo ? 'reply' : 'quill'} size="s" />{' '} 83 <time> 84 {!!replyTo && ( 85 <> 86 @{replyTo.account.acct} 87 <br /> 88 </> 89 )} 90 {niceDateTime(updatedAtDate)} 91 </time> 92 </b> 93 <MenuConfirm 94 confirmLabel={<span>Delete this draft?</span>} 95 menuItemClassName="danger" 96 align="end" 97 disabled={uiState === 'loading'} 98 onClick={() => { 99 (async () => { 100 try { 101 // const yes = confirm('Delete this draft?'); 102 // if (yes) { 103 await db.drafts.del(key); 104 reload(); 105 // } 106 } catch (e) { 107 alert('Error deleting draft! Please try again.'); 108 } 109 })(); 110 }} 111 > 112 <button 113 type="button" 114 class="small light" 115 disabled={uiState === 'loading'} 116 > 117 Delete&hellip; 118 </button> 119 </MenuConfirm> 120 </div> 121 <button 122 type="button" 123 disabled={uiState === 'loading'} 124 class="draft-item" 125 onClick={async () => { 126 // console.log({ draftStatus }); 127 let replyToStatus; 128 if (replyTo) { 129 setUIState('loading'); 130 try { 131 replyToStatus = await masto.v1.statuses 132 .$select(replyTo.id) 133 .fetch(); 134 } catch (e) { 135 console.error(e); 136 alert('Error fetching reply-to status!'); 137 setUIState('default'); 138 return; 139 } 140 setUIState('default'); 141 } 142 window.__COMPOSE__ = { 143 draftStatus, 144 replyToStatus, 145 }; 146 states.showCompose = true; 147 states.showDrafts = false; 148 }} 149 > 150 <MiniDraft draft={draft} /> 151 </button> 152 </li> 153 ); 154 })} 155 </ul> 156 {drafts.length > 1 && ( 157 <p> 158 <MenuConfirm 159 confirmLabel={<span>Delete all drafts?</span>} 160 menuItemClassName="danger" 161 disabled={uiState === 'loading'} 162 onClick={() => { 163 (async () => { 164 // const yes = confirm('Delete all drafts?'); 165 // if (yes) { 166 setUIState('loading'); 167 try { 168 await db.drafts.delMany( 169 drafts.map((draft) => draft.key), 170 ); 171 setUIState('default'); 172 reload(); 173 } catch (e) { 174 console.error(e); 175 alert('Error deleting drafts! Please try again.'); 176 setUIState('error'); 177 } 178 // } 179 })(); 180 }} 181 > 182 <button 183 type="button" 184 class="light danger" 185 disabled={uiState === 'loading'} 186 > 187 Delete all&hellip; 188 </button> 189 </MenuConfirm> 190 </p> 191 )} 192 </> 193 ) : ( 194 <p>No drafts found.</p> 195 )} 196 </main> 197 </div> 198 ); 199} 200 201function MiniDraft({ draft }) { 202 const { draftStatus, replyTo } = draft; 203 const { status, spoilerText, poll, mediaAttachments } = draftStatus; 204 const hasPoll = poll?.options?.length > 0; 205 const hasMedia = mediaAttachments?.length > 0; 206 const hasPollOrMedia = hasPoll || hasMedia; 207 const firstImageMedia = useMemo(() => { 208 if (!hasMedia) return; 209 const image = mediaAttachments.find((media) => /image/.test(media.type)); 210 if (!image) return; 211 const { file } = image; 212 const objectURL = URL.createObjectURL(file); 213 return objectURL; 214 }, [hasMedia, mediaAttachments]); 215 return ( 216 <> 217 <div class="mini-draft"> 218 {hasPollOrMedia && ( 219 <div 220 class={`mini-draft-aside ${firstImageMedia ? 'has-image' : ''}`} 221 style={ 222 firstImageMedia 223 ? { 224 '--bg-image': `url(${firstImageMedia})`, 225 } 226 : {} 227 } 228 > 229 {hasPoll && <Icon icon="poll" />} 230 {hasMedia && ( 231 <span> 232 <Icon icon="attachment" />{' '} 233 <small>{mediaAttachments?.length}</small> 234 </span> 235 )} 236 </div> 237 )} 238 <div class="mini-draft-main"> 239 {!!spoilerText && <div class="mini-draft-spoiler">{spoilerText}</div>} 240 {!!status && <div class="mini-draft-status">{status}</div>} 241 </div> 242 </div> 243 </> 244 ); 245} 246 247export default Drafts;