diff --git a/src/lib/media/picker.web.tsx b/src/lib/media/picker.web.tsx
index aaa08463..746feaed 100644
--- a/src/lib/media/picker.web.tsx
+++ b/src/lib/media/picker.web.tsx
@@ -1,6 +1,5 @@
///
-import {CropImageModal} from 'state/models/shell-ui'
import {PickerOpts, CameraOpts, CropperOpts, PickedMedia} from './types'
export type {PickedMedia} from './types'
import {RootStoreModel} from 'state/index'
@@ -51,15 +50,17 @@ export async function openCropper(
): Promise {
// TODO handle more opts
return new Promise((resolve, reject) => {
- store.shell.openModal(
- new CropImageModal(opts.path, (img?: PickedMedia) => {
+ store.shell.openModal({
+ name: 'crop-image',
+ uri: opts.path,
+ onSelect: (img?: PickedMedia) => {
if (img) {
resolve(img)
} else {
reject(new Error('Canceled'))
}
- }),
- )
+ },
+ })
})
}
diff --git a/src/state/models/shell-ui.ts b/src/state/models/shell-ui.ts
index b9f480ec..640bed0b 100644
--- a/src/state/models/shell-ui.ts
+++ b/src/state/models/shell-ui.ts
@@ -2,75 +2,57 @@ import {RootStoreModel} from './root-store'
import {makeAutoObservable} from 'mobx'
import {ProfileViewModel} from './profile-view'
import {isObj, hasProp} from 'lib/type-guards'
-import {PickedMedia} from 'view/com/util/images/image-crop-picker/types'
+import {PickedMedia} from 'lib/media/types'
-export class ConfirmModal {
- name = 'confirm'
-
- constructor(
- public title: string,
- public message: string | (() => JSX.Element),
- public onPressConfirm: () => void | Promise,
- ) {
- makeAutoObservable(this)
- }
+export interface ConfirmModal {
+ name: 'confirm'
+ title: string
+ message: string | (() => JSX.Element)
+ onPressConfirm: () => void | Promise
}
-export class EditProfileModal {
- name = 'edit-profile'
-
- constructor(
- public profileView: ProfileViewModel,
- public onUpdate?: () => void,
- ) {
- makeAutoObservable(this)
- }
+export interface EditProfileModal {
+ name: 'edit-profile'
+ profileView: ProfileViewModel
+ onUpdate?: () => void
}
-export class ServerInputModal {
- name = 'server-input'
-
- constructor(
- public initialService: string,
- public onSelect: (url: string) => void,
- ) {
- makeAutoObservable(this)
- }
+export interface ServerInputModal {
+ name: 'server-input'
+ initialService: string
+ onSelect: (url: string) => void
}
-export class ReportPostModal {
- name = 'report-post'
-
- constructor(public postUri: string, public postCid: string) {
- makeAutoObservable(this)
- }
+export interface ReportPostModal {
+ name: 'report-post'
+ postUri: string
+ postCid: string
}
-export class ReportAccountModal {
- name = 'report-account'
-
- constructor(public did: string) {
- makeAutoObservable(this)
- }
+export interface ReportAccountModal {
+ name: 'report-account'
+ did: string
}
-export class CropImageModal {
- name = 'crop-image'
-
- constructor(
- public uri: string,
- public onSelect: (img?: PickedMedia) => void,
- ) {}
+export interface CropImageModal {
+ name: 'crop-image'
+ uri: string
+ onSelect: (img?: PickedMedia) => void
}
-export class DeleteAccountModal {
- name = 'delete-account'
-
- constructor() {
- makeAutoObservable(this)
- }
+export interface DeleteAccountModal {
+ name: 'delete-account'
}
+export type Modal =
+ | ConfirmModal
+ | EditProfileModal
+ | ServerInputModal
+ | ReportPostModal
+ | ReportAccountModal
+ | CropImageModal
+ | DeleteAccountModal
+
interface LightboxModel {}
export class ProfileImageLightbox implements LightboxModel {
@@ -111,15 +93,7 @@ export class ShellUiModel {
minimalShellMode = false
isMainMenuOpen = false
isModalActive = false
- activeModal:
- | ConfirmModal
- | EditProfileModal
- | ServerInputModal
- | ReportPostModal
- | ReportAccountModal
- | CropImageModal
- | DeleteAccountModal
- | undefined
+ activeModals: Modal[] = []
isLightboxActive = false
activeLightbox: ProfileImageLightbox | ImagesLightbox | undefined
isComposerActive = false
@@ -159,24 +133,15 @@ export class ShellUiModel {
this.isMainMenuOpen = v
}
- openModal(
- modal:
- | ConfirmModal
- | EditProfileModal
- | ServerInputModal
- | ReportPostModal
- | ReportAccountModal
- | CropImageModal
- | DeleteAccountModal,
- ) {
+ openModal(modal: Modal) {
this.rootStore.emitNavigation()
this.isModalActive = true
- this.activeModal = modal
+ this.activeModals.push(modal)
}
closeModal() {
- this.isModalActive = false
- this.activeModal = undefined
+ this.activeModals.pop()
+ this.isModalActive = this.activeModals.length > 0
}
openLightbox(lightbox: ProfileImageLightbox | ImagesLightbox) {
diff --git a/src/view/com/lightbox/ImageViewing/index.tsx b/src/view/com/lightbox/ImageViewing/index.tsx
index 83259330..722540a5 100644
--- a/src/view/com/lightbox/ImageViewing/index.tsx
+++ b/src/view/com/lightbox/ImageViewing/index.tsx
@@ -17,7 +17,7 @@ import {
VirtualizedList,
ModalProps,
} from 'react-native'
-import {Modal} from '../../modals/Modal'
+import {ModalsContainer} from '../../modals/Modal'
import ImageItem from './components/ImageItem/ImageItem'
import ImageDefaultHeader from './components/ImageDefaultHeader'
@@ -98,7 +98,7 @@ function ImageViewing({
return (
-
+
{typeof HeaderComponent !== 'undefined' ? (
diff --git a/src/view/com/login/CreateAccount.tsx b/src/view/com/login/CreateAccount.tsx
index 6dc93f5e..870013f3 100644
--- a/src/view/com/login/CreateAccount.tsx
+++ b/src/view/com/login/CreateAccount.tsx
@@ -25,7 +25,6 @@ import {makeValidHandle, createFullHandle} from 'lib/strings/handles'
import {toNiceDomain} from 'lib/strings/url-helpers'
import {useStores, DEFAULT_SERVICE} from 'state/index'
import {ServiceDescription} from 'state/models/session'
-import {ServerInputModal} from 'state/models/shell-ui'
import {usePalette} from 'lib/hooks/usePalette'
import {cleanError} from 'lib/strings/errors'
@@ -84,7 +83,11 @@ export const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => {
const onPressRetryConnect = () => setRetryDescribeTrigger({})
const onPressSelectService = () => {
- store.shell.openModal(new ServerInputModal(serviceUrl, setServiceUrl))
+ store.shell.openModal({
+ name: 'server-input',
+ initialService: serviceUrl,
+ onSelect: setServiceUrl,
+ })
Keyboard.dismiss()
}
diff --git a/src/view/com/login/Signin.tsx b/src/view/com/login/Signin.tsx
index eed17311..a311e299 100644
--- a/src/view/com/login/Signin.tsx
+++ b/src/view/com/login/Signin.tsx
@@ -279,7 +279,11 @@ const LoginForm = ({
const [password, setPassword] = useState('')
const onPressSelectService = () => {
- store.shell.openModal(new ServerInputModal(serviceUrl, setServiceUrl))
+ store.shell.openModal({
+ name: 'server-input',
+ initialService: serviceUrl,
+ onSelect: setServiceUrl,
+ })
Keyboard.dismiss()
track('Signin:PressedSelectService')
}
diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx
index 2529d0d5..d4c8ada6 100644
--- a/src/view/com/modals/Modal.tsx
+++ b/src/view/com/modals/Modal.tsx
@@ -5,8 +5,6 @@ import BottomSheet from '@gorhom/bottom-sheet'
import {useStores} from 'state/index'
import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop'
-import * as models from 'state/models/shell-ui'
-
import * as ConfirmModal from './Confirm'
import * as EditProfileModal from './EditProfile'
import * as ServerInputModal from './ServerInput'
@@ -32,52 +30,37 @@ export const Modal = observer(function Modal() {
store.shell.closeModal()
}
+ const activeModal = React.useMemo(
+ () => store.shell.activeModals.at(-1),
+ [store.shell.activeModals],
+ )
+
useEffect(() => {
if (store.shell.isModalActive) {
bottomSheetRef.current?.expand()
} else {
bottomSheetRef.current?.close()
}
- }, [store.shell.isModalActive, bottomSheetRef, store.shell.activeModal?.name])
+ }, [store.shell.isModalActive, bottomSheetRef, activeModal?.name])
let snapPoints: (string | number)[] = CLOSED_SNAPPOINTS
let element
- if (store.shell.activeModal?.name === 'confirm') {
+ if (activeModal?.name === 'confirm') {
snapPoints = ConfirmModal.snapPoints
- element = (
-
- )
- } else if (store.shell.activeModal?.name === 'edit-profile') {
+ element =
+ } else if (activeModal?.name === 'edit-profile') {
snapPoints = EditProfileModal.snapPoints
- element = (
-
- )
- } else if (store.shell.activeModal?.name === 'server-input') {
+ element =
+ } else if (activeModal?.name === 'server-input') {
snapPoints = ServerInputModal.snapPoints
- element = (
-
- )
- } else if (store.shell.activeModal?.name === 'report-post') {
+ element =
+ } else if (activeModal?.name === 'report-post') {
snapPoints = ReportPostModal.snapPoints
- element = (
-
- )
- } else if (store.shell.activeModal?.name === 'report-account') {
+ element =
+ } else if (activeModal?.name === 'report-account') {
snapPoints = ReportAccountModal.snapPoints
- element = (
-
- )
- } else if (store.shell.activeModal?.name === 'delete-account') {
+ element =
+ } else if (activeModal?.name === 'delete-account') {
snapPoints = DeleteAccountModal.snapPoints
element =
} else {
diff --git a/src/view/com/modals/Modal.web.tsx b/src/view/com/modals/Modal.web.tsx
index 2af02695..38b526d2 100644
--- a/src/view/com/modals/Modal.web.tsx
+++ b/src/view/com/modals/Modal.web.tsx
@@ -3,8 +3,7 @@ import {TouchableWithoutFeedback, StyleSheet, View} from 'react-native'
import {observer} from 'mobx-react-lite'
import {useStores} from 'state/index'
import {usePalette} from 'lib/hooks/usePalette'
-
-import * as models from 'state/models/shell-ui'
+import type {Modal as ModalIface} from 'state/models/shell-ui'
import * as ConfirmModal from './Confirm'
import * as EditProfileModal from './EditProfile'
@@ -13,7 +12,23 @@ import * as ReportPostModal from './ReportPost'
import * as ReportAccountModal from './ReportAccount'
import * as CropImageModal from './crop-image/CropImage.web'
-export const Modal = observer(function Modal() {
+export const ModalsContainer = observer(function ModalsContainer() {
+ const store = useStores()
+
+ if (!store.shell.isModalActive) {
+ return null
+ }
+
+ return (
+ <>
+ {store.shell.activeModals.map((modal, i) => (
+
+ ))}
+ >
+ )
+})
+
+function Modal({modal}: {modal: ModalIface}) {
const store = useStores()
const pal = usePalette('default')
@@ -22,7 +37,7 @@ export const Modal = observer(function Modal() {
}
const onPressMask = () => {
- if (store.shell.activeModal?.name === 'crop-image') {
+ if (modal.name === 'crop-image') {
return // dont close on mask presses during crop
}
store.shell.closeModal()
@@ -32,42 +47,18 @@ export const Modal = observer(function Modal() {
}
let element
- if (store.shell.activeModal?.name === 'confirm') {
- element = (
-
- )
- } else if (store.shell.activeModal?.name === 'edit-profile') {
- element = (
-
- )
- } else if (store.shell.activeModal?.name === 'server-input') {
- element = (
-
- )
- } else if (store.shell.activeModal?.name === 'report-post') {
- element = (
-
- )
- } else if (store.shell.activeModal?.name === 'report-account') {
- element = (
-
- )
- } else if (store.shell.activeModal?.name === 'crop-image') {
- element = (
-
- )
+ if (modal.name === 'confirm') {
+ element =
+ } else if (modal.name === 'edit-profile') {
+ element =
+ } else if (modal.name === 'server-input') {
+ element =
+ } else if (modal.name === 'report-post') {
+ element =
+ } else if (modal.name === 'report-account') {
+ element =
+ } else if (modal.name === 'crop-image') {
+ element =
} else {
return null
}
@@ -81,7 +72,7 @@ export const Modal = observer(function Modal() {
)
-})
+}
const styles = StyleSheet.create({
mask: {
diff --git a/src/view/com/profile/ProfileHeader.tsx b/src/view/com/profile/ProfileHeader.tsx
index 0ca6e1e7..087b1f39 100644
--- a/src/view/com/profile/ProfileHeader.tsx
+++ b/src/view/com/profile/ProfileHeader.tsx
@@ -15,11 +15,7 @@ import {
import {BlurView} from '../util/BlurView'
import {ProfileViewModel} from 'state/models/profile-view'
import {useStores} from 'state/index'
-import {
- EditProfileModal,
- ReportAccountModal,
- ProfileImageLightbox,
-} from 'state/models/shell-ui'
+import {ProfileImageLightbox} from 'state/models/shell-ui'
import {pluralize} from 'lib/strings/helpers'
import {toShareUrl} from 'lib/strings/url-helpers'
import {s, gradients} from 'lib/styles'
@@ -65,7 +61,11 @@ export const ProfileHeader = observer(function ProfileHeader({
}
const onPressEditProfile = () => {
track('ProfileHeader:EditProfileButtonClicked')
- store.shell.openModal(new EditProfileModal(view, onRefreshAll))
+ store.shell.openModal({
+ name: 'edit-profile',
+ profileView: view,
+ onUpdate: onRefreshAll,
+ })
}
const onPressFollowers = () => {
track('ProfileHeader:FollowersButtonClicked')
@@ -101,7 +101,10 @@ export const ProfileHeader = observer(function ProfileHeader({
}
const onPressReportAccount = () => {
track('ProfileHeader:ReportAccountButtonClicked')
- store.shell.openModal(new ReportAccountModal(view.did))
+ store.shell.openModal({
+ name: 'report-account',
+ did: view.did,
+ })
}
// loading
diff --git a/src/view/com/util/UserAvatar.tsx b/src/view/com/util/UserAvatar.tsx
index 0c5c9d25..5a7a4801 100644
--- a/src/view/com/util/UserAvatar.tsx
+++ b/src/view/com/util/UserAvatar.tsx
@@ -18,6 +18,7 @@ import {useStores} from 'state/index'
import {colors, gradients} from 'lib/styles'
import {DropdownButton} from './forms/DropdownButton'
import {usePalette} from 'lib/hooks/usePalette'
+import {isWeb} from 'platform/detection'
export function UserAvatar({
size,
@@ -58,7 +59,7 @@ export function UserAvatar({
)
const dropdownItems = [
- {
+ !isWeb && {
label: 'Camera',
icon: 'camera' as IconProp,
onPress: async () => {
diff --git a/src/view/com/util/UserBanner.tsx b/src/view/com/util/UserBanner.tsx
index c17294f3..06a80d45 100644
--- a/src/view/com/util/UserBanner.tsx
+++ b/src/view/com/util/UserBanner.tsx
@@ -18,6 +18,7 @@ import {
} from 'lib/permissions'
import {DropdownButton} from './forms/DropdownButton'
import {usePalette} from 'lib/hooks/usePalette'
+import {isWeb} from 'platform/detection'
export function UserBanner({
banner,
@@ -29,7 +30,7 @@ export function UserBanner({
const store = useStores()
const pal = usePalette('default')
const dropdownItems = [
- {
+ !isWeb && {
label: 'Camera',
icon: 'camera' as IconProp,
onPress: async () => {
diff --git a/src/view/com/util/forms/DropdownButton.tsx b/src/view/com/util/forms/DropdownButton.tsx
index 8fddd594..2946c5ca 100644
--- a/src/view/com/util/forms/DropdownButton.tsx
+++ b/src/view/com/util/forms/DropdownButton.tsx
@@ -16,7 +16,6 @@ import {Button, ButtonType} from './Button'
import {colors} from 'lib/styles'
import {toShareUrl} from 'lib/strings/url-helpers'
import {useStores} from 'state/index'
-import {ReportPostModal, ConfirmModal} from 'state/models/shell-ui'
import {TABS_ENABLED} from 'lib/build-flags'
import {usePalette} from 'lib/hooks/usePalette'
import {useTheme} from 'lib/ThemeContext'
@@ -28,6 +27,7 @@ export interface DropdownItem {
label: string
onPress: () => void
}
+type MaybeDropdownItem = DropdownItem | false | undefined
export type DropdownButtonType = ButtonType | 'bare'
@@ -44,7 +44,7 @@ export function DropdownButton({
}: {
type?: DropdownButtonType
style?: StyleProp
- items: DropdownItem[]
+ items: MaybeDropdownItem[]
label?: string
menuWidth?: number
children?: React.ReactNode
@@ -71,7 +71,12 @@ export function DropdownButton({
? pageX + width + rightOffset
: pageX + width - menuWidth
const newY = pageY + height + bottomOffset
- createDropdownMenu(newX, newY, menuWidth, items)
+ createDropdownMenu(
+ newX,
+ newY,
+ menuWidth,
+ items.filter(v => !!v) as DropdownItem[],
+ )
},
)
}
@@ -151,7 +156,11 @@ export function PostDropdownBtn({
icon: 'circle-exclamation',
label: 'Report post',
onPress() {
- store.shell.openModal(new ReportPostModal(itemUri, itemCid))
+ store.shell.openModal({
+ name: 'report-post',
+ postUri: itemUri,
+ postCid: itemCid,
+ })
},
},
isAuthor
@@ -159,13 +168,12 @@ export function PostDropdownBtn({
icon: ['far', 'trash-can'],
label: 'Delete post',
onPress() {
- store.shell.openModal(
- new ConfirmModal(
- 'Delete this post?',
- 'Are you sure? This can not be undone.',
- onDeletePost,
- ),
- )
+ store.shell.openModal({
+ name: 'confirm',
+ title: 'Delete this post?',
+ message: 'Are you sure? This can not be undone.',
+ onPressConfirm: onDeletePost,
+ })
},
}
: undefined,
diff --git a/src/view/shell/mobile/index.tsx b/src/view/shell/mobile/index.tsx
index 0b3921b7..da8e73a6 100644
--- a/src/view/shell/mobile/index.tsx
+++ b/src/view/shell/mobile/index.tsx
@@ -28,7 +28,7 @@ import {Login} from '../../screens/Login'
import {Menu} from './Menu'
import {Onboard} from '../../screens/Onboard'
import {HorzSwipe} from '../../com/util/gestures/HorzSwipe'
-import {Modal} from '../../com/modals/Modal'
+import {ModalsContainer} from '../../com/modals/Modal'
import {Lightbox} from '../../com/lightbox/Lightbox'
import {Text} from '../../com/util/text/Text'
import {ErrorBoundary} from '../../com/util/ErrorBoundary'
@@ -366,7 +366,7 @@ export const MobileShell: React.FC = observer(() => {
return (
-
+
)
}
@@ -515,7 +515,7 @@ export const MobileShell: React.FC = observer(() => {
notificationCount={store.me.notifications.unreadCount}
/>
-
+
{
return (
-
+
)
}
@@ -67,7 +67,7 @@ export const WebShell: React.FC = observer(() => {
imagesOpen={store.shell.composerOpts?.imagesOpen}
onPost={store.shell.composerOpts?.onPost}
/>
-
+
)