diff --git a/libs/remix-ui/app/src/lib/remix-app/actions/modals.ts b/libs/remix-ui/app/src/lib/remix-app/actions/modals.ts new file mode 100644 index 0000000000..4d11831bc6 --- /dev/null +++ b/libs/remix-ui/app/src/lib/remix-app/actions/modals.ts @@ -0,0 +1,30 @@ +import { AppModal } from '../interface' + +export type ActionMap = { + [Key in keyof M]: M[Key] extends undefined + ? { + type: Key; + } + : { + type: Key; + payload: M[Key]; + } +} + +export const enum actionTypes { + setModal = 'SET_MODAL', + setToast = 'SET_TOAST', + handleHideModal = 'HANDLE_HIDE_MODAL', + handleToaster = 'HANDLE_HIDE_TOAST' +} + +type ModalPayload = { + [actionTypes.setModal]: AppModal + [actionTypes.handleHideModal]: any + [actionTypes.setToast]: string + [actionTypes.handleToaster]: any +} + +export type ModalAction = ActionMap[keyof ActionMap< + ModalPayload +>] diff --git a/libs/remix-ui/app/src/lib/remix-app/dragbar/dragbar.css b/libs/remix-ui/app/src/lib/remix-app/components/dragbar/dragbar.css similarity index 100% rename from libs/remix-ui/app/src/lib/remix-app/dragbar/dragbar.css rename to libs/remix-ui/app/src/lib/remix-app/components/dragbar/dragbar.css diff --git a/libs/remix-ui/app/src/lib/remix-app/dragbar/dragbar.tsx b/libs/remix-ui/app/src/lib/remix-app/components/dragbar/dragbar.tsx similarity index 100% rename from libs/remix-ui/app/src/lib/remix-app/dragbar/dragbar.tsx rename to libs/remix-ui/app/src/lib/remix-app/components/dragbar/dragbar.tsx diff --git a/libs/remix-ui/app/src/lib/remix-app/components/modals/dialogs.tsx b/libs/remix-ui/app/src/lib/remix-app/components/modals/dialogs.tsx new file mode 100644 index 0000000000..2e1dd0cc1d --- /dev/null +++ b/libs/remix-ui/app/src/lib/remix-app/components/modals/dialogs.tsx @@ -0,0 +1,27 @@ +import React, { useEffect } from 'react' +import { useDialogDispatchers, useDialogs } from '../../context/provider' +import { Toaster } from '@remix-ui/toaster' +import ModalWrapper from './modal-wrapper' +import { ModalTypes } from '../../types' + +const AppDialogs = () => { + const { modal, toast, alert, handleHideModal, handleToaster } = useDialogDispatchers() + const { focusModal, focusToaster } = useDialogs() + + const ok = (returnedValue) => { + console.log('ok', returnedValue) + } + + useEffect(() => { + modal('test', 'Enter gist url', 'yes', ok, 'cancel', () => { }, ModalTypes.prompt, 'wss://remix.ethereum.org:8545') + toast('test toast') + // alert('fat error') + }, []) + + return ( + <> + + + ) +} +export default AppDialogs diff --git a/libs/remix-ui/app/src/lib/remix-app/modals/matomo.tsx b/libs/remix-ui/app/src/lib/remix-app/components/modals/matomo.tsx similarity index 97% rename from libs/remix-ui/app/src/lib/remix-app/modals/matomo.tsx rename to libs/remix-ui/app/src/lib/remix-app/components/modals/matomo.tsx index 2ab56fa02d..cf95d1d4c7 100644 --- a/libs/remix-ui/app/src/lib/remix-app/modals/matomo.tsx +++ b/libs/remix-ui/app/src/lib/remix-app/components/modals/matomo.tsx @@ -1,6 +1,6 @@ import React, { useContext, useEffect, useState } from 'react' import { ModalDialog } from '@remix-ui/modal-dialog' -import AppContext from '../context/context' +import { AppContext } from '../../context/context' const _paq = window._paq = window._paq || [] const MatomoDialog = (props) => { diff --git a/libs/remix-ui/app/src/lib/remix-app/components/modals/modal-wrapper.tsx b/libs/remix-ui/app/src/lib/remix-app/components/modals/modal-wrapper.tsx new file mode 100644 index 0000000000..b5a6c1d807 --- /dev/null +++ b/libs/remix-ui/app/src/lib/remix-app/components/modals/modal-wrapper.tsx @@ -0,0 +1,54 @@ +import React, { useEffect, useRef, useState } from 'react' +import { ModalDialog } from '@remix-ui/modal-dialog' +import { ModalDialogProps } from 'libs/remix-ui/modal-dialog/src/lib/types' +import { ModalTypes } from '../../types' + +interface ModalWrapperProps extends ModalDialogProps { + modalType?: ModalTypes + defaultValue?: string +} + +const ModalWrapper = (props: ModalWrapperProps) => { + const [state, setState] = useState() + const ref = useRef() + + const onFinishPrompt = async () => { + if (ref.current === undefined) { + props.okFn() + } else { + // @ts-ignore: Object is possibly 'null'. + props.okFn(ref.current.value) + } + } + + const createModalMessage = () => { + return ( + <> + {props.message} + + ) + } + + useEffect(() => { + console.log(props) + if (props.modalType) { + switch (props.modalType) { + case ModalTypes.prompt: + case ModalTypes.password: + setState({ + ...props, + okFn: onFinishPrompt, + message: createModalMessage() + }) + break + default: + setState({ ...props }) + break + } + } + }, [props]) + + return ( + ) +} +export default ModalWrapper diff --git a/libs/remix-ui/app/src/lib/remix-app/modals/alert.tsx b/libs/remix-ui/app/src/lib/remix-app/components/modals/origin-warning.tsx similarity index 96% rename from libs/remix-ui/app/src/lib/remix-app/modals/alert.tsx rename to libs/remix-ui/app/src/lib/remix-app/components/modals/origin-warning.tsx index b61abbb1d1..19c8f41c27 100644 --- a/libs/remix-ui/app/src/lib/remix-app/modals/alert.tsx +++ b/libs/remix-ui/app/src/lib/remix-app/components/modals/origin-warning.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react' import { ModalDialog } from '@remix-ui/modal-dialog' -const AlertModal = () => { +const OriginWarning = () => { const [visible, setVisible] = useState(true) const [content, setContent] = useState('') @@ -40,4 +40,4 @@ const AlertModal = () => { cancelFn={closeModal}>{content}) } -export default AlertModal +export default OriginWarning diff --git a/libs/remix-ui/app/src/lib/remix-app/modals/splashscreen.tsx b/libs/remix-ui/app/src/lib/remix-app/components/splashscreen.tsx similarity index 100% rename from libs/remix-ui/app/src/lib/remix-app/modals/splashscreen.tsx rename to libs/remix-ui/app/src/lib/remix-app/components/splashscreen.tsx diff --git a/libs/remix-ui/app/src/lib/remix-app/context/context.tsx b/libs/remix-ui/app/src/lib/remix-app/context/context.tsx index f9f97a4555..986ba645f3 100644 --- a/libs/remix-ui/app/src/lib/remix-app/context/context.tsx +++ b/libs/remix-ui/app/src/lib/remix-app/context/context.tsx @@ -1,5 +1,15 @@ import React from 'react' +import { ModalInitialState } from '../state/modals' +import { ModalTypes } from '../types' -const AppContext = React.createContext(null) +export const AppContext = React.createContext(null) -export default AppContext +export const dispatchModalContext = React.createContext({ + modal: (title: string, message: string | JSX.Element, okLabel: string, okFn: (value?:any) => void, cancelLabel?: string, cancelFn?: () => void, modalType?: ModalTypes, defaultValue?: string) => { }, + toast: (message: string) => {}, + alert: (title: string, message?: string | JSX.Element) => {}, + handleHideModal: () => { }, + handleToaster: () => {} +}) + +export const modalContext = React.createContext(ModalInitialState) diff --git a/libs/remix-ui/app/src/lib/remix-app/context/provider.tsx b/libs/remix-ui/app/src/lib/remix-app/context/provider.tsx new file mode 100644 index 0000000000..e590c60631 --- /dev/null +++ b/libs/remix-ui/app/src/lib/remix-app/context/provider.tsx @@ -0,0 +1,62 @@ +import React, { useReducer } from 'react' +import { actionTypes } from '../actions/modals' +import { modalReducer } from '../reducer/modals' +import { ModalInitialState } from '../state/modals' +import { ModalTypes } from '../types' +import { AppContext, dispatchModalContext, modalContext } from './context' + +export const ModalProvider = ({ children = [], reducer = modalReducer, initialState = ModalInitialState } = {}) => { + const [{ modals, toasters, focusModal, focusToaster }, dispatch] = useReducer(reducer, initialState) + + const modal = (title: string, message: string | JSX.Element, okLabel: string, okFn: (value?:any) => void, cancelLabel?: string, cancelFn?: () => void, modalType?: ModalTypes, defaultValue?: string) => { + dispatch({ + type: actionTypes.setModal, + payload: { title, message, okLabel, okFn, cancelLabel, cancelFn, modalType: modalType || ModalTypes.default, defaultValue: defaultValue } + }) + } + + const alert = (title: string, message?: string | JSX.Element) => { + modal(message ? title : 'Alert', message || title, 'OK', null, null, null) + } + + const handleHideModal = () => { + dispatch({ + type: actionTypes.handleHideModal, + payload: null + }) + } + + const toast = (message: string) => { + dispatch({ + type: actionTypes.setToast, + payload: message + }) + } + + const handleToaster = () => { + dispatch({ + type: actionTypes.handleToaster, + payload: null + }) + } + + return ( + + {children} + + ) +} + +export const AppProvider = ({ children = [], value = {} } = {}) => { + return + {children} + +} + +export const useDialogs = () => { + return React.useContext(modalContext) +} + +export const useDialogDispatchers = () => { + return React.useContext(dispatchModalContext) +} diff --git a/libs/remix-ui/app/src/lib/remix-app/interface/index.ts b/libs/remix-ui/app/src/lib/remix-app/interface/index.ts new file mode 100644 index 0000000000..4be233453a --- /dev/null +++ b/libs/remix-ui/app/src/lib/remix-app/interface/index.ts @@ -0,0 +1,21 @@ +import { ModalTypes } from '../types' + +export interface AppModal { + hide?: boolean + title: string + // eslint-disable-next-line no-undef + message: string | JSX.Element + okLabel: string + okFn: (value?:any) => void + cancelLabel: string + cancelFn: () => void, + modalType?: ModalTypes, + defaultValue?: string +} + +export interface ModalState { + modals: AppModal[], + toasters: string[], + focusModal: AppModal, + focusToaster: string +} diff --git a/libs/remix-ui/app/src/lib/remix-app/reducer/modals.ts b/libs/remix-ui/app/src/lib/remix-app/reducer/modals.ts index 582d8305b0..d2f8ac1b39 100644 --- a/libs/remix-ui/app/src/lib/remix-app/reducer/modals.ts +++ b/libs/remix-ui/app/src/lib/remix-app/reducer/modals.ts @@ -1,43 +1,48 @@ -import { actionTypes } from '../state/modals' -import { Modal } from '../types' +import { actionTypes, ModalAction } from '../actions/modals' +import { ModalInitialState } from '../state/modals' +import { AppModal, ModalState } from '../interface' -interface Action { - type: string - payload: any -} - -export interface ModalState { - modals: Modal[], - toasters: string[], - focusModal: Modal, - focusToaster: string -} +export const modalReducer = (state: ModalState = ModalInitialState, action: ModalAction) => { + console.log(action) + switch (action.type) { + case actionTypes.setModal: { + console.log('set modal', action, Date.now()) + let modalList:AppModal[] = state.modals + modalList.push(action.payload) + if (state.modals.length === 1 && state.focusModal.hide === true) { // if it's the first one show it + const focusModal: AppModal = { + hide: false, + title: modalList[0].title, + message: modalList[0].message, + okLabel: modalList[0].okLabel, + okFn: modalList[0].okFn, + cancelLabel: modalList[0].cancelLabel, + cancelFn: modalList[0].cancelFn, + modalType: modalList[0].modalType, + defaultValue: modalList[0].defaultValue + } -const ModalInitialState: ModalState = { - modals: [], - toasters: [], - focusModal: { - hide: true, - title: '', - message: '', - okLabel: '', - okFn: () => {}, - cancelLabel: '', - cancelFn: () => {} - }, - focusToaster: '' -} + modalList = modalList.slice() + modalList.shift() + return { ...state, modals: modalList, focusModal: focusModal } + } + return { ...state, modals: modalList } + } + case actionTypes.handleHideModal: + console.log('handle hid', JSON.stringify(state.modals)) + state.focusModal = { ...state.focusModal, hide: true, message: null } + return { ...state } -export const modalReducer = (state: ModalState = ModalInitialState, action: Action) => { - switch (action.type) { - case actionTypes.setModal: - state.modals.push(action.payload) + case actionTypes.setToast: + state.toasters.push(action.payload) + if (state.toasters.length > 0) { + const focus = state.toasters[0] + state.toasters.shift() + return { ...state, focusToaster: focus } + } return { ...state } - } - return { - modals: state.modals, - toasters: state.toasters, - focusModal: state.focusModal, - focusToaster: state.focusToaster + + case actionTypes.handleToaster: + return { ...state, focusToaster: '' } } } diff --git a/libs/remix-ui/app/src/lib/remix-app/remix-app.tsx b/libs/remix-ui/app/src/lib/remix-app/remix-app.tsx index d6931a52e5..21e243d32a 100644 --- a/libs/remix-ui/app/src/lib/remix-app/remix-app.tsx +++ b/libs/remix-ui/app/src/lib/remix-app/remix-app.tsx @@ -1,13 +1,12 @@ -import React, { useContext, useEffect, useRef, useState } from 'react' +import React, { useEffect, useRef, useState } from 'react' import './style/remix-app.css' -import RemixSplashScreen from './modals/splashscreen' -import MatomoDialog from './modals/matomo' -import AlertModal from './modals/alert' -import AppContext from './context/context' -import DragBar from './dragbar/dragbar' -import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line -import { Toaster } from '@remix-ui/toaster' // eslint-disable-line -import { Modal } from './types' +import RemixSplashScreen from './components/splashscreen' +import MatomoDialog from './components/modals/matomo' +import OriginWarning from './components/modals/origin-warning' +import DragBar from './components/dragbar/dragbar' +import { AppProvider } from './context/provider' +import AppDialogs from './components/modals/dialogs' + interface IRemixAppUi { app: any } @@ -19,19 +18,6 @@ const RemixApp = (props: IRemixAppUi) => { const mainPanelRef = useRef(null) const iconPanelRef = useRef(null) const hiddenPanelRef = useRef(null) - // modals - const [focusModal, setFocusModal] = useState({ - hide: true, - title: '', - message: '', - okLabel: '', - okFn: () => {}, - cancelLabel: '', - cancelFn: () => {} - }) - const [modals, setModals] = useState([]) - const [focusToaster, setFocusToaster] = useState('') - const [toasters, setToasters] = useState([]) useEffect(() => { if (sidePanelRef.current) { @@ -77,36 +63,6 @@ const RemixApp = (props: IRemixAppUi) => { }) } - const handleHideModal = () => { - setFocusModal(modal => { - return { ...modal, hide: true, message: null } - }) - } - - // eslint-disable-next-line no-undef - const modal = (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => { - setModals(modals => { - modals.push({ message, title, okLabel, okFn, cancelLabel, cancelFn }) - return [...modals] - }) - } - - export const clearPopUp = async () => { - dispatch(hidePopUp()) - } - - const handleToaster = () => { - setFocusToaster('') - clearPopUp() - } - - const toast = (toasterMsg: string) => { - setToasters(messages => { - messages.push(toasterMsg) - return [...messages] - }) - } - const components = { iconPanel:
, sidePanel:
, @@ -121,9 +77,9 @@ const RemixApp = (props: IRemixAppUi) => { } return ( - + - +
@@ -134,8 +90,8 @@ const RemixApp = (props: IRemixAppUi) => {
{components.hiddenPanel} -
- + + ) } diff --git a/libs/remix-ui/app/src/lib/remix-app/state/modals.ts b/libs/remix-ui/app/src/lib/remix-app/state/modals.ts index 70ed815c1f..f824b42136 100644 --- a/libs/remix-ui/app/src/lib/remix-app/state/modals.ts +++ b/libs/remix-ui/app/src/lib/remix-app/state/modals.ts @@ -1,48 +1,16 @@ -import { useReducer } from 'react' -import { modalReducer } from '../reducer/modals' +import { ModalState } from '../interface' -export const actionTypes = { - setModal: 'SET_MODAL', - setToast: 'SET_TOAST' -} - -export const setModal = (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => { - return { - type: actionTypes.setModal, - payload: { message, title, okLabel, okFn, cancelLabel, cancelFn } - } -} - -export const setToast = (toasterMsg: string) => { - return { - type: actionTypes.setToast, - payload: toasterMsg - } -} - -function useModals ({ reducer = modalReducer } = {}) { - const [{ modals, toasters, focusModal, focusToaster }, dispatch] = useReducer(reducer, { - modals: [], - toasters: [], - focusModal: { - hide: true, - title: '', - message: '', - okLabel: '', - okFn: () => {}, - cancelLabel: '', - cancelFn: () => {} - }, - focusToaster: '' - }) - - const modal = (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => { - dispatch(setModal(title, message, okLabel, okFn, cancelLabel, cancelFn)) - } - - const toast = (toasterMsg: string) => { - dispatch(setToast(toasterMsg)) - } - - return { focusModal, modals, focusToaster, toasters, modal, toast } +export const ModalInitialState: ModalState = { + modals: [], + toasters: [], + focusModal: { + hide: true, + title: '', + message: '', + okLabel: '', + okFn: () => { }, + cancelLabel: '', + cancelFn: () => { } + }, + focusToaster: '' } diff --git a/libs/remix-ui/app/src/lib/remix-app/types/index.ts b/libs/remix-ui/app/src/lib/remix-app/types/index.ts index 9127068891..edca9e147c 100644 --- a/libs/remix-ui/app/src/lib/remix-app/types/index.ts +++ b/libs/remix-ui/app/src/lib/remix-app/types/index.ts @@ -1,11 +1,7 @@ - -export interface Modal { - hide?: boolean - title: string - // eslint-disable-next-line no-undef - message: string | JSX.Element - okLabel: string - okFn: () => void - cancelLabel: string - cancelFn: () => void - } \ No newline at end of file +export const enum ModalTypes { + alert = 'alert', + confirm = 'confirm', + prompt = 'prompt', + password = 'password', + default = 'default', +} diff --git a/libs/remix-ui/modal-dialog/src/lib/types/index.ts b/libs/remix-ui/modal-dialog/src/lib/types/index.ts index ccdd374ef4..4f55c82b41 100644 --- a/libs/remix-ui/modal-dialog/src/lib/types/index.ts +++ b/libs/remix-ui/modal-dialog/src/lib/types/index.ts @@ -4,7 +4,7 @@ export interface ModalDialogProps { title?: string, message?: string | JSX.Element, okLabel?: string, - okFn?: () => void, + okFn?: (value?:any) => void, cancelLabel?: string, cancelFn?: () => void, modalClass?: string,