commit
648604282e
@ -0,0 +1,44 @@ |
||||
import { Plugin } from '@remixproject/engine' |
||||
import { LibraryProfile, MethodApi, StatusEvents } from '@remixproject/plugin-utils' |
||||
import { AppModal } from '@remix-ui/app' |
||||
import { AlertModal } from 'libs/remix-ui/app/src/lib/remix-app/interface' |
||||
import { dispatchModalInterface } from 'libs/remix-ui/app/src/lib/remix-app/context/context' |
||||
|
||||
interface IModalApi { |
||||
events: StatusEvents, |
||||
methods: { |
||||
modal: (args: AppModal) => void |
||||
alert: (args: AlertModal) => void |
||||
toast: (message: string) => void |
||||
} |
||||
} |
||||
|
||||
const profile:LibraryProfile<IModalApi> = { |
||||
name: 'modal', |
||||
displayName: 'Modal', |
||||
description: 'Modal', |
||||
methods: ['modal', 'alert', 'toast'] |
||||
} |
||||
|
||||
export class ModalPlugin extends Plugin implements MethodApi<IModalApi> { |
||||
dispatcher: dispatchModalInterface |
||||
constructor () { |
||||
super(profile) |
||||
} |
||||
|
||||
setDispatcher (dispatcher: dispatchModalInterface) { |
||||
this.dispatcher = dispatcher |
||||
} |
||||
|
||||
async modal (args: AppModal) { |
||||
this.dispatcher.modal(args) |
||||
} |
||||
|
||||
async alert (args: AlertModal) { |
||||
this.dispatcher.alert(args) |
||||
} |
||||
|
||||
async toast (message: string) { |
||||
this.dispatcher.toast(message) |
||||
} |
||||
} |
@ -0,0 +1,53 @@ |
||||
import { Plugin } from '@remixproject/engine' |
||||
import { Profile } from '@remixproject/plugin-utils' |
||||
import { AlertModal } from 'libs/remix-ui/app/src/lib/remix-app/interface' |
||||
import { ModalTypes } from 'libs/remix-ui/app/src/lib/remix-app/types' |
||||
import { AppModal } from '../../../../../libs/remix-ui/app/src' |
||||
|
||||
const profile:Profile = { |
||||
name: 'testerplugin', |
||||
displayName: 'testerplugin', |
||||
description: 'testerplugin', |
||||
methods: [] |
||||
} |
||||
|
||||
export class ModalPluginTester extends Plugin { |
||||
constructor () { |
||||
super(profile) |
||||
} |
||||
|
||||
handleMessage (message: any): void { |
||||
console.log(message) |
||||
} |
||||
|
||||
onActivation (): void { |
||||
// just a modal
|
||||
let mod:AppModal = { |
||||
id: 'modal1', |
||||
title: 'test', |
||||
message: 'test', |
||||
okFn: this.handleMessage, |
||||
okLabel: 'yes', |
||||
cancelFn: null, |
||||
cancelLabel: 'no' |
||||
} |
||||
// this.call('modal', 'modal', mod)
|
||||
|
||||
// modal with callback
|
||||
mod = { ...mod, message: 'gist url', modalType: ModalTypes.prompt, defaultValue: 'prompting' } |
||||
// this.call('modal', 'modal', mod)
|
||||
|
||||
// modal with password
|
||||
mod = { ...mod, message: 'enter password to give me eth', modalType: ModalTypes.password, defaultValue: 'pass' } |
||||
// this.call('modal', 'modal', mod)
|
||||
|
||||
const al:AlertModal = { |
||||
id: 'myalert', |
||||
message: 'alert message' |
||||
} |
||||
// this.call('modal', 'alert', al)
|
||||
|
||||
// set toaster
|
||||
// this.call('modal', 'toast', 'toast message')
|
||||
} |
||||
} |
@ -1 +1,4 @@ |
||||
export { default as RemixApp } from './lib/remix-app/remix-app' |
||||
export { dispatchModalContext } from './lib/remix-app/context/context' |
||||
export { ModalProvider } from './lib/remix-app/context/provider' |
||||
export { AppModal } from './lib/remix-app/interface/index' |
||||
|
@ -0,0 +1,30 @@ |
||||
import { AppModal } from '../interface' |
||||
|
||||
type ActionMap<M extends { [index: string]: any }> = { |
||||
[Key in keyof M]: M[Key] extends undefined |
||||
? { |
||||
type: Key; |
||||
} |
||||
: { |
||||
type: Key; |
||||
payload: M[Key]; |
||||
} |
||||
} |
||||
|
||||
export const enum modalActionTypes { |
||||
setModal = 'SET_MODAL', |
||||
setToast = 'SET_TOAST', |
||||
handleHideModal = 'HANDLE_HIDE_MODAL', |
||||
handleToaster = 'HANDLE_HIDE_TOAST' |
||||
} |
||||
|
||||
type ModalPayload = { |
||||
[modalActionTypes.setModal]: AppModal |
||||
[modalActionTypes.handleHideModal]: any |
||||
[modalActionTypes.setToast]: string |
||||
[modalActionTypes.handleToaster]: any |
||||
} |
||||
|
||||
export type ModalAction = ActionMap<ModalPayload>[keyof ActionMap< |
||||
ModalPayload |
||||
>] |
@ -0,0 +1,16 @@ |
||||
import React, { useContext, useEffect } from 'react' |
||||
import { AppContext } from '../../context/context' |
||||
import { useDialogDispatchers } from '../../context/provider' |
||||
|
||||
const DialogViewPlugin = () => { |
||||
const { modal, alert, toast } = useDialogDispatchers() |
||||
const app = useContext(AppContext) |
||||
|
||||
useEffect(() => { |
||||
console.log(modal, app) |
||||
app.modal.setDispatcher({ modal, alert, toast }) |
||||
}, []) |
||||
return <></> |
||||
} |
||||
|
||||
export default DialogViewPlugin |
@ -0,0 +1,16 @@ |
||||
import React from 'react' |
||||
import { useDialogDispatchers, useDialogs } from '../../context/provider' |
||||
import { Toaster } from '@remix-ui/toaster' |
||||
import ModalWrapper from './modal-wrapper' |
||||
|
||||
const AppDialogs = () => { |
||||
const { handleHideModal, handleToaster } = useDialogDispatchers() |
||||
const { focusModal, focusToaster } = useDialogs() |
||||
|
||||
return ( |
||||
<> |
||||
<ModalWrapper {...focusModal} handleHide={handleHideModal}></ModalWrapper> |
||||
<Toaster message={focusToaster} handleHide={handleToaster} /> |
||||
</>) |
||||
} |
||||
export default AppDialogs |
@ -0,0 +1,45 @@ |
||||
import React, { useContext, useEffect, useState } from 'react' |
||||
import { AppContext } from '../../context/context' |
||||
import { useDialogDispatchers } from '../../context/provider' |
||||
const _paq = window._paq = window._paq || [] |
||||
|
||||
const MatomoDialog = (props) => { |
||||
const { settings, showMatamo, appManager } = useContext(AppContext) |
||||
const { modal } = useDialogDispatchers() |
||||
const [visible, setVisible] = useState<boolean>(props.hide) |
||||
|
||||
const message = () => { |
||||
return (<><p>An Opt-in version of <a href="https://matomo.org" target="_blank" rel="noreferrer">Matomo</a>, an open source data analytics platform is being used to improve Remix IDE.</p> |
||||
<p>We realize that our users have sensitive information in their code and that their privacy - your privacy - must be protected.</p> |
||||
<p>All data collected through Matomo is stored on our own server - no data is ever given to third parties. Our analytics reports are public: <a href="https://matomo.ethereum.org/index.php?module=MultiSites&action=index&idSite=23&period=day&date=yesterday" target="_blank" rel="noreferrer">take a look</a>.</p> |
||||
<p>We do not collect nor store any personally identifiable information (PII).</p> |
||||
<p>For more info, see: <a href="https://medium.com/p/66ef69e14931/" target="_blank" rel="noreferrer">Matomo Analyitcs on Remix iDE</a>.</p> |
||||
<p>You can change your choice in the Settings panel anytime.</p></>) |
||||
} |
||||
|
||||
useEffect(() => { |
||||
if (visible && showMatamo) { |
||||
modal({ id: 'matomoModal', title: 'Help us to improve Remix IDE', message: message(), okLabel: 'Accept', okFn: handleModalOkClick, cancelLabel: 'Decline', cancelFn: declineModal }) |
||||
} |
||||
}, [visible]) |
||||
|
||||
const declineModal = async () => { |
||||
settings.updateMatomoAnalyticsChoice(false) |
||||
_paq.push(['optUserOut']) |
||||
appManager.call('walkthrough', 'start') |
||||
setVisible(false) |
||||
} |
||||
|
||||
const handleModalOkClick = async () => { |
||||
_paq.push(['forgetUserOptOut']) |
||||
// @TODO remove next line when https://github.com/matomo-org/matomo/commit/9e10a150585522ca30ecdd275007a882a70c6df5 is used
|
||||
document.cookie = 'mtm_consent_removed=; expires=Thu, 01 Jan 1970 00:00:01 GMT;' |
||||
settings.updateMatomoAnalyticsChoice(true) |
||||
appManager.call('walkthrough', 'start') |
||||
setVisible(false) |
||||
} |
||||
|
||||
return (<></>) |
||||
} |
||||
|
||||
export default MatomoDialog |
@ -0,0 +1,56 @@ |
||||
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<ModalDialogProps>() |
||||
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 = (defaultValue: string) => { |
||||
return ( |
||||
<> |
||||
{props.message} |
||||
<input type={props.modalType === ModalTypes.password ? 'password' : 'text'} defaultValue={defaultValue} data-id="modalDialogCustomPromp" ref={ref} className="form-control" /></> |
||||
) |
||||
} |
||||
|
||||
useEffect(() => { |
||||
if (props.modalType) { |
||||
switch (props.modalType) { |
||||
case ModalTypes.prompt: |
||||
case ModalTypes.password: |
||||
setState({ |
||||
...props, |
||||
okFn: onFinishPrompt, |
||||
message: createModalMessage(props.defaultValue) |
||||
}) |
||||
break |
||||
default: |
||||
setState({ ...props }) |
||||
break |
||||
} |
||||
} else { |
||||
setState({ ...props }) |
||||
} |
||||
}, [props]) |
||||
|
||||
return ( |
||||
<ModalDialog id={props.id} {...state} handleHide={props.handleHide} /> |
||||
) |
||||
} |
||||
export default ModalWrapper |
@ -1,5 +1,24 @@ |
||||
import React from 'react' |
||||
import { AlertModal, AppModal } from '../interface' |
||||
import { ModalInitialState } from '../state/modals' |
||||
import { ModalTypes } from '../types' |
||||
|
||||
const AppContext = React.createContext(null) |
||||
export const AppContext = React.createContext<any>(null) |
||||
|
||||
export default AppContext |
||||
export interface dispatchModalInterface { |
||||
modal: (data: AppModal) => void |
||||
toast: (message: string) => void |
||||
alert: (data: AlertModal) => void |
||||
handleHideModal: () => void, |
||||
handleToaster: () => void |
||||
} |
||||
|
||||
export const dispatchModalContext = React.createContext<dispatchModalInterface>({ |
||||
modal: (data: AppModal) => { }, |
||||
toast: (message: string) => {}, |
||||
alert: (data: AlertModal) => {}, |
||||
handleHideModal: () => {}, |
||||
handleToaster: () => {} |
||||
}) |
||||
|
||||
export const modalContext = React.createContext(ModalInitialState) |
||||
|
@ -0,0 +1,64 @@ |
||||
import React, { useReducer } from 'react' |
||||
import { modalActionTypes } from '../actions/modals' |
||||
import { AlertModal, AppModal } from '../interface' |
||||
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 = (data: AppModal) => { |
||||
const { id, title, message, okLabel, okFn, cancelLabel, cancelFn, modalType, defaultValue } = data |
||||
dispatch({ |
||||
type: modalActionTypes.setModal, |
||||
payload: { id, title, message, okLabel, okFn, cancelLabel, cancelFn, modalType: modalType || ModalTypes.default, defaultValue: defaultValue } |
||||
}) |
||||
} |
||||
|
||||
const alert = (data: AlertModal) => { |
||||
modal({ id: data.id, title: data.title || 'Alert', message: data.message || data.title, okLabel: 'OK', okFn: (value?:any) => {}, cancelLabel: '', cancelFn: () => {} }) |
||||
} |
||||
|
||||
const handleHideModal = () => { |
||||
dispatch({ |
||||
type: modalActionTypes.handleHideModal, |
||||
payload: null |
||||
}) |
||||
} |
||||
|
||||
const toast = (message: string) => { |
||||
dispatch({ |
||||
type: modalActionTypes.setToast, |
||||
payload: message |
||||
}) |
||||
} |
||||
|
||||
const handleToaster = () => { |
||||
dispatch({ |
||||
type: modalActionTypes.handleToaster, |
||||
payload: null |
||||
}) |
||||
} |
||||
|
||||
return (<dispatchModalContext.Provider value={{ modal, toast, alert, handleHideModal, handleToaster }}> |
||||
<modalContext.Provider value={{ modals, toasters, focusModal, focusToaster }}> |
||||
{children} |
||||
</modalContext.Provider> |
||||
</dispatchModalContext.Provider>) |
||||
} |
||||
|
||||
export const AppProvider = ({ children = [], value = {} } = {}) => { |
||||
return <AppContext.Provider value={value}> |
||||
<ModalProvider>{children}</ModalProvider> |
||||
</AppContext.Provider> |
||||
} |
||||
|
||||
export const useDialogs = () => { |
||||
return React.useContext(modalContext) |
||||
} |
||||
|
||||
export const useDialogDispatchers = () => { |
||||
return React.useContext(dispatchModalContext) |
||||
} |
@ -0,0 +1,28 @@ |
||||
import { ModalTypes } from '../types' |
||||
|
||||
export interface AppModal { |
||||
id: string |
||||
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 AlertModal { |
||||
id: string |
||||
title?: string, |
||||
message: string | JSX.Element, |
||||
} |
||||
|
||||
export interface ModalState { |
||||
modals: AppModal[], |
||||
toasters: string[], |
||||
focusModal: AppModal, |
||||
focusToaster: string |
||||
} |
@ -1,51 +0,0 @@ |
||||
import React, { useContext, useEffect, useState } from 'react' |
||||
import { ModalDialog } from '@remix-ui/modal-dialog' |
||||
import AppContext from '../context/context' |
||||
const _paq = window._paq = window._paq || [] |
||||
|
||||
const MatomoDialog = (props) => { |
||||
const { settings, showMatamo, appManager } = useContext(AppContext) |
||||
const [visible, setVisible] = useState<boolean>(props.hide) |
||||
useEffect(() => { |
||||
if (showMatamo) { |
||||
setVisible(true) |
||||
} else { |
||||
setVisible(false) |
||||
} |
||||
}, []) |
||||
const declineModal = async () => { |
||||
settings.updateMatomoAnalyticsChoice(false) |
||||
_paq.push(['optUserOut']) |
||||
appManager.call('walkthrough', 'start') |
||||
setVisible(false) |
||||
} |
||||
const hideModal = async () => { |
||||
setVisible(false) |
||||
} |
||||
const handleModalOkClick = async () => { |
||||
_paq.push(['forgetUserOptOut']) |
||||
// @TODO remove next line when https://github.com/matomo-org/matomo/commit/9e10a150585522ca30ecdd275007a882a70c6df5 is used
|
||||
document.cookie = 'mtm_consent_removed=; expires=Thu, 01 Jan 1970 00:00:01 GMT;' |
||||
settings.updateMatomoAnalyticsChoice(true) |
||||
appManager.call('walkthrough', 'start') |
||||
setVisible(false) |
||||
} |
||||
return (<ModalDialog |
||||
handleHide={hideModal} |
||||
id="matomoDialog" |
||||
hide={!visible} |
||||
title="Help us to improve Remix IDE" |
||||
okLabel="Accept" |
||||
okFn={ handleModalOkClick } |
||||
cancelLabel="Decline" |
||||
cancelFn={declineModal}> |
||||
<p>An Opt-in version of <a href="https://matomo.org" target="_blank" rel="noreferrer">Matomo</a>, an open source data analytics platform is being used to improve Remix IDE.</p> |
||||
<p>We realize that our users have sensitive information in their code and that their privacy - your privacy - must be protected.</p> |
||||
<p>All data collected through Matomo is stored on our own server - no data is ever given to third parties. Our analytics reports are public: <a href="https://matomo.ethereum.org/index.php?module=MultiSites&action=index&idSite=23&period=day&date=yesterday" target="_blank" rel="noreferrer">take a look</a>.</p> |
||||
<p>We do not collect nor store any personally identifiable information (PII).</p> |
||||
<p>For more info, see: <a href="https://medium.com/p/66ef69e14931/" target="_blank" rel="noreferrer">Matomo Analyitcs on Remix iDE</a>.</p> |
||||
<p>You can change your choice in the Settings panel anytime.</p> |
||||
</ModalDialog>) |
||||
} |
||||
|
||||
export default MatomoDialog |
@ -0,0 +1,46 @@ |
||||
import { modalActionTypes, ModalAction } from '../actions/modals' |
||||
import { ModalInitialState } from '../state/modals' |
||||
import { AppModal, ModalState } from '../interface' |
||||
|
||||
export const modalReducer = (state: ModalState = ModalInitialState, action: ModalAction) => { |
||||
switch (action.type) { |
||||
case modalActionTypes.setModal: { |
||||
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 = { |
||||
id: modalList[0].id, |
||||
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 |
||||
} |
||||
|
||||
modalList = modalList.slice() |
||||
modalList.shift() |
||||
return { ...state, modals: modalList, focusModal: focusModal } |
||||
} |
||||
return { ...state, modals: modalList } |
||||
} |
||||
case modalActionTypes.handleHideModal: |
||||
state.focusModal = { ...state.focusModal, hide: true, message: null } |
||||
return { ...state } |
||||
|
||||
case modalActionTypes.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 } |
||||
|
||||
case modalActionTypes.handleToaster: |
||||
return { ...state, focusToaster: '' } |
||||
} |
||||
} |
@ -0,0 +1,17 @@ |
||||
import { ModalState } from '../interface' |
||||
|
||||
export const ModalInitialState: ModalState = { |
||||
modals: [], |
||||
toasters: [], |
||||
focusModal: { |
||||
id: '', |
||||
hide: true, |
||||
title: '', |
||||
message: '', |
||||
okLabel: '', |
||||
okFn: () => { }, |
||||
cancelLabel: '', |
||||
cancelFn: () => { } |
||||
}, |
||||
focusToaster: '' |
||||
} |
@ -0,0 +1,7 @@ |
||||
export const enum ModalTypes { |
||||
alert = 'alert', |
||||
confirm = 'confirm', |
||||
prompt = 'prompt', |
||||
password = 'password', |
||||
default = 'default', |
||||
} |
Loading…
Reference in new issue