apply fixes

pull/5247/head
bunsenstraat 2 months ago committed by Aniket
parent 519ab72314
commit ed64c89dd8
  1. 492
      apps/remix-ide-e2e/src/tests/matomo.test.ts
  2. 28
      apps/remix-ide/src/app.js
  3. 2
      apps/remix-ide/src/app/components/preload.tsx
  4. 2
      apps/remix-ide/src/app/tabs/settings-tab.tsx
  5. 20
      apps/remix-ide/src/assets/js/loader.js
  6. 2
      libs/remix-ui/app/src/index.ts
  7. 19
      libs/remix-ui/app/src/lib/remix-app/components/modals/matomo.tsx
  8. 8
      libs/remix-ui/app/src/lib/remix-app/components/modals/modal-wrapper.tsx
  9. 2
      libs/remix-ui/app/src/lib/remix-app/context/context.tsx
  10. 6
      libs/remix-ui/app/src/lib/remix-app/context/provider.tsx
  11. 7
      libs/remix-ui/app/src/lib/remix-app/interface/index.ts
  12. 3
      libs/remix-ui/app/src/lib/remix-app/reducer/modals.ts
  13. 4
      libs/remix-ui/app/src/lib/remix-app/remix-app.tsx
  14. 9
      libs/remix-ui/app/src/lib/remix-app/types/index.ts
  15. 23
      libs/remix-ui/modal-dialog/src/lib/remix-ui-modal-dialog.tsx
  16. 7
      libs/remix-ui/modal-dialog/src/lib/types/index.ts
  17. 2
      libs/remix-ui/settings/src/lib/remix-ui-settings.tsx
  18. 1
      libs/remix-ui/settings/src/lib/settingsAction.ts

@ -0,0 +1,492 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
import examples from '../examples/example-contracts'
const sources = [
{ 'Untitled.sol': { content: examples.ballot.content } }
]
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', false)
},
'confirm Matomo #group1': function (browser: NightwatchBrowser) {
browser.perform((done) => {
browser
.execute(function () {
localStorage.removeItem('config-v0.8:.remix.config')
localStorage.setItem('showMatomo', 'true')
}, [])
.refreshPage()
.perform(done())
})
.waitForElementPresent({
selector: `//*[@data-id='compilerloaded']`,
locateStrategy: 'xpath',
timeout: 120000
})
.execute(function () {
return (window as any)._paq
}, [], (res) => {
console.log('_paq', res)
})
.waitForElementVisible('*[data-id="matomoModalModalDialogModalBody-react"]')
.pause(1000)
.click('[data-id="matomoModal-modal-footer-ok-react"]') // submitted
.execute(function () {
return (window as any)._paq
}, [], (res) => {
console.log('_paq', res)
})
.waitForElementNotVisible('*[data-id="matomoModalModalDialogModalBody-react"]')
.waitForElementVisible('*[data-id="beginnerbtn"]', 10000)
.pause(1000)
.click('[data-id="beginnerbtn"]')
.waitForElementNotPresent('*[data-id="beginnerbtn"]')
.waitForElementVisible({
selector: `//*[contains(text(), 'Welcome to Remix IDE')]`,
locateStrategy: 'xpath'
})
.refreshPage()
.waitForElementPresent({
selector: `//*[@data-id='compilerloaded']`,
locateStrategy: 'xpath',
timeout: 120000
})
.waitForElementNotPresent('*[data-id="matomoModalModalDialogModalBody-react"]')
.clickLaunchIcon('settings')
.verify.elementPresent('[id="settingsMatomoAnalytics"]:checked')
.execute(function () {
return JSON.parse(window.localStorage.getItem('config-v0.8:.remix.config'))['settings/matomo-analytics'] == true
}, [], (res) => {
console.log('res', res)
browser.assert.ok((res as any).value, 'matomo analytics is enabled')
})
},
'decline Matomo #group1': function (browser: NightwatchBrowser) {
browser.perform((done) => {
browser.execute(function () {
localStorage.removeItem('config-v0.8:.remix.config')
localStorage.setItem('showMatomo', 'true')
localStorage.removeItem('matomo-analytics-consent')
}, [])
.refreshPage()
.perform(done())
})
.waitForElementPresent({
selector: `//*[@data-id='compilerloaded']`,
locateStrategy: 'xpath',
timeout: 120000
})
.execute(function () {
return (window as any)._paq
}, [], (res) => {
console.log('_paq', res)
})
.waitForElementVisible('*[data-id="matomoModalModalDialogModalBody-react"]')
.click('[data-id="matomoModal-modal-footer-cancel-react"]') // cancel
.execute(function () {
return (window as any)._paq
}, [], (res) => {
console.log('_paq', res)
})
.waitForElementNotVisible('*[data-id="matomoModalModalDialogModalBody-react"]')
.pause(2000)
.waitForElementNotPresent('*[data-id="beginnerbtn"]', 10000)
.clickLaunchIcon('settings')
.waitForElementNotPresent('[id="settingsMatomoAnalytics"]:checked')
.execute(function () {
return JSON.parse(window.localStorage.getItem('config-v0.8:.remix.config'))['settings/matomo-analytics'] == false
}, [], (res) => {
console.log('res', res)
browser.assert.ok((res as any).value, 'matomo analytics is disabled')
})
},
'blur matomo #group2': function (browser: NightwatchBrowser) {
browser.perform((done) => {
browser.execute(function () {
localStorage.removeItem('config-v0.8:.remix.config')
localStorage.setItem('showMatomo', 'true')
localStorage.removeItem('matomo-analytics-consent')
}, [])
.refreshPage()
.perform(done())
})
.waitForElementPresent({
selector: `//*[@data-id='compilerloaded']`,
locateStrategy: 'xpath',
timeout: 120000
})
.waitForElementVisible({
selector: '*[data-id="matomoModalModalDialogModalBody-react"]',
abortOnFailure: true
})
.waitForElementVisible('*[data-id="matomoModal-modal-close"]')
.click('[data-id="matomoModal-modal-close"]')
.waitForElementNotVisible('*[data-id="matomoModalModalDialogModalBody-react"]')
.pause(2000)
.waitForElementNotPresent('*[data-id="beginnerbtn"]', 10000)
.clickLaunchIcon('settings')
.waitForElementNotPresent('[id="settingsMatomoAnalytics"]:checked')
.execute(function () {
return JSON.parse(window.localStorage.getItem('config-v0.8:.remix.config'))['settings/matomo-analytics'] == undefined
}, [], (res) => {
console.log('res', res)
browser.assert.ok((res as any).value, 'matomo analytics is undefined')
})
},
'matomo should reappear #group2': function (browser: NightwatchBrowser) {
browser
.refreshPage()
.waitForElementPresent({
selector: `//*[@data-id='compilerloaded']`,
locateStrategy: 'xpath',
timeout: 120000
})
.waitForElementVisible({
selector: '*[data-id="matomoModalModalDialogModalBody-react"]',
abortOnFailure: true
})
.waitForElementVisible('*[data-id="matomoModal-modal-close"]')
.click('[data-id="matomoModal-modal-close"]')
.waitForElementNotVisible('*[data-id="matomoModalModalDialogModalBody-react"]')
},
'change settings #group2': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('settings')
.waitForElementVisible('*[data-id="label-matomo-settings"]')
.pause(1000)
.click('*[data-id="label-matomo-settings"]')
.refreshPage()
.waitForElementPresent({
selector: `//*[@data-id='compilerloaded']`,
locateStrategy: 'xpath',
timeout: 120000
})
.waitForElementNotPresent('*[data-id="matomoModalModalDialogModalBody-react"]')
},
'should get enter dialog again #group2': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="beginnerbtn"]', 10000)
.pause(1000)
.click('[data-id="beginnerbtn"]')
.waitForElementNotPresent('*[data-id="beginnerbtn"]')
.waitForElementVisible({
selector: `//*[contains(text(), 'Welcome to Remix IDE')]`,
locateStrategy: 'xpath'
})
.waitForElementVisible('*[id="remixTourSkipbtn"]')
.click('*[id="remixTourSkipbtn"]')
.clickLaunchIcon('settings')
.waitForElementPresent('[id="settingsMatomoAnalytics"]:checked')
.execute(function () {
return JSON.parse(window.localStorage.getItem('config-v0.8:.remix.config'))['settings/matomo-analytics'] == true
}, [], (res) => {
console.log('res', res)
browser.assert.ok((res as any).value, 'matomo analytics is enabled')
})
},
'decline Matomo and check timestamp #group3': function (browser: NightwatchBrowser) {
browser.perform((done) => {
browser.execute(function () {
localStorage.removeItem('config-v0.8:.remix.config')
localStorage.setItem('showMatomo', 'true')
localStorage.removeItem('matomo-analytics-consent')
}, [])
.refreshPage()
.perform(done())
})
.waitForElementPresent({
selector: `//*[@data-id='compilerloaded']`,
locateStrategy: 'xpath',
timeout: 120000
})
// output the contents of the storage
.execute(function () {
return {
consent: window.localStorage.getItem('matomo-analytics-consent'),
config: window.localStorage.getItem('config-v0.8:.remix.config'),
showMatomo: window.localStorage.getItem('showMatomo')
}
}, [], (res) => {
console.log('res', res)
})
.waitForElementVisible('*[data-id="matomoModalModalDialogModalBody-react"]')
.click('[data-id="matomoModal-modal-footer-cancel-react"]') // cancel
.waitForElementNotVisible('*[data-id="matomoModalModalDialogModalBody-react"]')
.pause(2000)
.execute(function () {
const timestamp = window.localStorage.getItem('matomo-analytics-consent');
if (timestamp) {
const consentDate = new Date(Number(timestamp));
// validate it is actually a date
if (isNaN(consentDate.getTime())) {
return false;
}
const now = new Date();
console.log('timestamp', timestamp, consentDate, now.getTime())
const diffInMinutes = (now.getTime() - consentDate.getTime()) / (1000 * 60);
console.log('diffInMinutes', diffInMinutes)
return diffInMinutes < 2;
}
return false;
}, [], (res) => {
console.log('res', res)
browser.assert.ok((res as any).value, 'matomo analytics consent timestamp is set')
})
},
'check old timestamp and reappear Matomo #group3': function (browser: NightwatchBrowser) {
browser.perform((done) => {
browser.execute(function () {
const oldTimestamp = new Date()
oldTimestamp.setMonth(oldTimestamp.getMonth() - 7)
localStorage.setItem('matomo-analytics-consent', oldTimestamp.getTime().toString())
}, [])
.refreshPage()
.perform(done())
})
.waitForElementPresent({
selector: `//*[@data-id='compilerloaded']`,
locateStrategy: 'xpath',
timeout: 120000
})
.execute(function () {
const timestamp = window.localStorage.getItem('matomo-analytics-consent');
if (timestamp) {
const consentDate = new Date(Number(timestamp));
// validate it is actually a date
if (isNaN(consentDate.getTime())) {
return false;
}
// validate it's older than 6 months
const now = new Date();
const diffInMonths = (now.getFullYear() - consentDate.getFullYear()) * 12 + now.getMonth() - consentDate.getMonth();
console.log('timestamp', timestamp, consentDate, now.getTime())
console.log('diffInMonths', diffInMonths)
return diffInMonths > 6;
}
return false;
}, [], (res) => {
console.log('res', res)
browser.assert.ok((res as any).value, 'matomo analytics consent timestamp is set')
})
.waitForElementVisible('*[data-id="matomoModalModalDialogModalBody-react"]')
.click('[data-id="matomoModal-modal-footer-cancel-react"]') // cancel
.waitForElementNotVisible('*[data-id="matomoModalModalDialogModalBody-react"]')
},
'check recent timestamp and do not reappear Matomo #group3': function (browser: NightwatchBrowser) {
browser.perform((done) => {
browser.execute(function () {
const recentTimestamp = new Date()
recentTimestamp.setMonth(recentTimestamp.getMonth() - 1)
localStorage.setItem('matomo-analytics-consent', recentTimestamp.getTime().toString())
}, [])
.refreshPage()
.perform(done())
})
// check if timestamp is younger than 6 months
.execute(function () {
const timestamp = window.localStorage.getItem('matomo-analytics-consent');
if (timestamp) {
const consentDate = new Date(Number(timestamp));
// validate it is actually a date
if (isNaN(consentDate.getTime())) {
return false;
}
// validate it's younger than 2 months
const now = new Date();
const diffInMonths = (now.getFullYear() - consentDate.getFullYear()) * 12 + now.getMonth() - consentDate.getMonth();
console.log('timestamp', timestamp, consentDate, now.getTime())
console.log('diffInMonths', diffInMonths)
return diffInMonths < 2;
}
return false;
}, [], (res) => {
console.log('res', res)
browser.assert.ok((res as any).value, 'matomo analytics consent timestamp is set')
})
.waitForElementPresent({
selector: `//*[@data-id='compilerloaded']`,
locateStrategy: 'xpath',
timeout: 120000
})
.pause(2000)
.waitForElementNotPresent('*[data-id="matomoModalModalDialogModalBody-react"]')
},
'accept Matomo and check timestamp #group3': function (browser: NightwatchBrowser) {
browser.perform((done) => {
browser.execute(function () {
localStorage.removeItem('config-v0.8:.remix.config')
localStorage.setItem('showMatomo', 'true')
localStorage.removeItem('matomo-analytics-consent')
}, [])
.refreshPage()
.perform(done())
})
.waitForElementPresent({
selector: `//*[@data-id='compilerloaded']`,
locateStrategy: 'xpath',
timeout: 120000
})
.waitForElementVisible('*[data-id="matomoModalModalDialogModalBody-react"]')
.click('[data-id="matomoModal-modal-footer-ok-react"]') // accept
.waitForElementNotVisible('*[data-id="matomoModalModalDialogModalBody-react"]')
.pause(2000)
.execute(function () {
const timestamp = window.localStorage.getItem('matomo-analytics-consent');
if (timestamp) {
const consentDate = new Date(Number(timestamp));
// validate it is actually a date
if (isNaN(consentDate.getTime())) {
return false;
}
const now = new Date();
console.log('timestamp', timestamp, consentDate, now.getTime())
const diffInMinutes = (now.getTime() - consentDate.getTime()) / (1000 * 60);
console.log('diffInMinutes', diffInMinutes)
return diffInMinutes < 1;
}
return false;
}, [], (res) => {
console.log('res', res)
browser.assert.ok((res as any).value, 'matomo analytics consent timestamp is to a recent date')
})
},
'check old timestamp and do not reappear Matomo after accept #group3': function (browser: NightwatchBrowser) {
browser.perform((done) => {
browser.execute(function () {
const oldTimestamp = new Date()
oldTimestamp.setMonth(oldTimestamp.getMonth() - 7)
localStorage.setItem('matomo-analytics-consent', oldTimestamp.getTime().toString())
}, [])
.refreshPage()
.perform(done())
})
.waitForElementPresent({
selector: `//*[@data-id='compilerloaded']`,
locateStrategy: 'xpath',
timeout: 120000
})
.pause(2000)
.waitForElementNotPresent('*[data-id="matomoModalModalDialogModalBody-react"]')
},
'check recent timestamp and do not reappear Matomo after accept #group3': function (browser: NightwatchBrowser) {
browser.perform((done) => {
browser.execute(function () {
const recentTimestamp = new Date()
recentTimestamp.setMonth(recentTimestamp.getMonth() - 1)
localStorage.setItem('matomo-analytics-consent', recentTimestamp.getTime().toString())
}, [])
.refreshPage()
.perform(done())
})
.waitForElementPresent({
selector: `//*[@data-id='compilerloaded']`,
locateStrategy: 'xpath',
timeout: 120000
})
.pause(2000)
.waitForElementNotPresent('*[data-id="matomoModalModalDialogModalBody-react"]')
},
'when there is a recent timestamp but no config the dialog should reappear #group3': function (browser: NightwatchBrowser) {
browser.perform((done) => {
browser.execute(function () {
localStorage.removeItem('config-v0.8:.remix.config')
const recentTimestamp = new Date()
recentTimestamp.setMonth(recentTimestamp.getMonth() - 1)
localStorage.setItem('matomo-analytics-consent', recentTimestamp.getTime().toString())
}, [])
.refreshPage()
.perform(done())
})
.waitForElementVisible('*[data-id="matomoModalModalDialogModalBody-react"]')
.click('[data-id="matomoModal-modal-footer-cancel-react"]') // cancel
.waitForElementNotVisible('*[data-id="matomoModalModalDialogModalBody-react"]')
},
'when there is a old timestamp but no config the dialog should reappear #group3': function (browser: NightwatchBrowser) {
browser.perform((done) => {
browser.execute(function () {
localStorage.removeItem('config-v0.8:.remix.config')
const oldTimestamp = new Date()
oldTimestamp.setMonth(oldTimestamp.getMonth() - 7)
localStorage.setItem('matomo-analytics-consent', oldTimestamp.getTime().toString())
}, [])
.refreshPage()
.perform(done())
})
.waitForElementVisible('*[data-id="matomoModalModalDialogModalBody-react"]')
.click('[data-id="matomoModal-modal-footer-cancel-react"]') // cancel
.waitForElementNotVisible('*[data-id="matomoModalModalDialogModalBody-react"]')
},
'verify Matomo events are tracked on app start #group4 #lfaky': function (browser: NightwatchBrowser) {
browser
.execute(function () {
return (window as any)._paq
}, [], (res) => {
const expectedEvents = [
["trackEvent", "Preload", "start"],
["trackEvent", "Storage", "activate", "indexedDB"],
["trackEvent", "App", "load"],
];
const actualEvents = (res as any).value;
const areEventsPresent = expectedEvents.every(expectedEvent =>
actualEvents.some(actualEvent =>
JSON.stringify(actualEvent) === JSON.stringify(expectedEvent)
)
);
browser.assert.ok(areEventsPresent, 'Matomo events are tracked correctly');
})
},
'@sources': function () {
return sources
},
'Add Ballot #group4': function (browser: NightwatchBrowser) {
browser
.addFile('Untitled.sol', sources[0]['Untitled.sol'])
},
'Deploy Ballot #group4': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('solidity')
.waitForElementVisible('*[data-id="compilerContainerCompileBtn"]')
.click('*[data-id="compilerContainerCompileBtn"]')
.testContracts('Untitled.sol', sources[0]['Untitled.sol'], ['Ballot'])
},
'verify Matomo compiler events are tracked #group4': function (browser: NightwatchBrowser) {
browser
.execute(function () {
return (window as any)._paq
}, [], (res) => {
const expectedEvent = ["trackEvent", "compiler", "compiled"];
const actualEvents = (res as any).value;
const isEventPresent = actualEvents.some(actualEvent =>
actualEvent[0] === expectedEvent[0] &&
actualEvent[1] === expectedEvent[1] &&
actualEvent[2] === expectedEvent[2] &&
actualEvent[3].startsWith("with_version_")
);
browser.assert.ok(isEventPresent, 'Matomo compiler events are tracked correctly');
})
},
}

@ -178,16 +178,30 @@ class AppComponent {
'6fd22d6fe5549ad4c4d8fd3ca0b7816b.mod': 35 // remix desktop
}
_paq.push(['trackEvent', 'App', 'load']);
this.matomoConfAlreadySet = Registry.getInstance().get('config').api.exists('settings/matomo-analytics')
this.matomoCurrentSetting = Registry.getInstance().get('config').api.get('settings/matomo-analytics')
let electronTracking = false
if (window.electronAPI) {
electronTracking = await window.electronAPI.canTrackMatomo()
}
this.showMatamo = (matomoDomains[window.location.hostname] || electronTracking) && !this.matomoConfAlreadySet
let electronTracking = window.electronAPI ? await window.electronAPI.canTrackMatomo() : false
const lastMatomoCheck = window.localStorage.getItem('matomo-analytics-consent')
const sixMonthsAgo = new Date();
sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 6);
this.showMatomo =
(matomoDomains[window.location.hostname] || electronTracking
|| (window.localStorage.getItem('showMatomo')
&& window.localStorage.getItem('showMatomo') === 'true'))
&& (!this.matomoConfAlreadySet
|| (this.matomoCurrentSetting === false
&& (!lastMatomoCheck || new Date(Number(lastMatomoCheck)) < sixMonthsAgo)
));
if(this.matomoCurrentSetting === false
&& (!lastMatomoCheck || new Date(Number(lastMatomoCheck)) < sixMonthsAgo)) {
_paq.push(['trackEvent', 'Matomo', 'refreshMatomoPermissions']);
}
this.walkthroughService = new WalkthroughService(appManager)

@ -10,6 +10,8 @@ import './styles/preload.css'
import isElectron from 'is-electron'
const _paq = (window._paq = window._paq || [])
_paq.push(['trackEvent', 'Preload', 'start'])
export const Preload = (props: any) => {
const [tip, setTip] = useState<string>('')
const [supported, setSupported] = useState<boolean>(true)

@ -109,6 +109,8 @@ module.exports = class SettingsTab extends ViewPlugin {
updateMatomoAnalyticsChoice(isChecked) {
this.config.set('settings/matomo-analytics', isChecked)
// set timestamp to local storage to track when the user has given consent
localStorage.setItem('matomo-analytics-consent', Date.now().toString())
this.useMatomoAnalytics = isChecked
if (!isChecked) {
// revoke tracking consent

@ -18,14 +18,26 @@ function trackDomain(domainToTrack) {
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
_paq.push(['enableHeartBeatTimer']);
if (!window.localStorage.getItem('config-v0.8:.remix.config') ||
(window.localStorage.getItem('config-v0.8:.remix.config') && !window.localStorage.getItem('config-v0.8:.remix.config').includes('settings/matomo-analytics'))) {
const remixConfig = window.localStorage.getItem('config-v0.8:.remix.config');
if (!remixConfig || (remixConfig && !remixConfig.includes('settings/matomo-analytics'))) {
// require user tracking consent before processing data
_paq.push(['requireConsent']);
} else {
// user has given consent to process their data
_paq.push(['setConsentGiven'])
try {
const config = JSON.parse(remixConfig);
if (config['settings/matomo-analytics'] === true) {
// user has given consent to process their data
_paq.push(['setConsentGiven']);
} else {
// user has not given consent to process their data
_paq.push(['requireConsent']);
}
} catch (e) {
console.error('Error parsing remix config:', e);
_paq.push(['requireConsent']);
}
}
_paq.push(['trackEvent', 'loader', 'load']);
(function () {
var u = "https://ethereumfoundation.matomo.cloud/";
_paq.push(['setTrackerUrl', u + 'matomo.php?debug=1']);

@ -3,5 +3,5 @@ export { dispatchModalContext, dispatchModalInterface, AppContext, appProviderCo
export { ModalProvider, useDialogDispatchers } from './lib/remix-app/context/provider'
export { AppModal } from './lib/remix-app/interface/index'
export { AlertModal } from './lib/remix-app/interface/index'
export { ModalTypes } from './lib/remix-app/types/index'
export { ModalTypes, AppModalCancelTypes } from './lib/remix-app/types/index'
export { AppAction, appActionTypes } from './lib/remix-app/actions/app'

@ -2,6 +2,7 @@ import React, { useContext, useEffect, useState } from 'react'
import { FormattedMessage } from 'react-intl'
import { AppContext } from '../../context/context'
import { useDialogDispatchers } from '../../context/provider'
import { AppModalCancelTypes } from '../../types'
declare global {
interface Window {
_paq: any
@ -15,7 +16,7 @@ interface MatomoDialogProps {
}
const MatomoDialog = (props: MatomoDialogProps) => {
const { settings, showMatamo, appManager } = useContext(AppContext)
const { settings, showMatomo, appManager } = useContext(AppContext)
const { modal } = useDialogDispatchers()
const [visible, setVisible] = useState<boolean>(props.hide)
@ -63,7 +64,7 @@ const MatomoDialog = (props: MatomoDialogProps) => {
}
useEffect(() => {
if (visible && showMatamo) {
if (visible && showMatomo) {
modal({
id: 'matomoModal',
title: <FormattedMessage id="remixApp.matomoTitle" />,
@ -72,18 +73,22 @@ const MatomoDialog = (props: MatomoDialogProps) => {
okFn: handleModalOkClick,
cancelLabel: <FormattedMessage id="remixApp.decline" />,
cancelFn: declineModal,
preventBlur: true
})
}
}, [visible])
const declineModal = async () => {
settings.updateMatomoAnalyticsChoice(false)
// revoke tracking consent
_paq.push(['forgetConsentGiven'])
setVisible(false)
const declineModal = async (reason: AppModalCancelTypes) => {
if (reason === AppModalCancelTypes.click || reason === AppModalCancelTypes.enter) {
settings.updateMatomoAnalyticsChoice(false)
// revoke tracking consent
_paq.push(['forgetConsentGiven'])
setVisible(false)
}
}
const handleModalOkClick = async () => {
// user has given consent to process their data
_paq.push(['setConsentGiven'])
settings.updateMatomoAnalyticsChoice(true)

@ -1,6 +1,6 @@
import React, { useEffect, useRef, useState } from 'react'
import { ModalDialog, ModalDialogProps, ValidationResult } from '@remix-ui/modal-dialog'
import { ModalTypes } from '../../types'
import { AppModalCancelTypes, ModalTypes } from '../../types'
interface ModalWrapperProps extends ModalDialogProps {
modalType?: ModalTypes
@ -42,8 +42,8 @@ const ModalWrapper = (props: ModalWrapperProps) => {
props.okFn ? props.okFn(data.current) : props.resolve(data.current || true)
}
const onCancelFn = async () => {
props.cancelFn ? props.cancelFn() : props.resolve(false)
const onCancelFn = async (reason?: AppModalCancelTypes) => {
props.cancelFn ? props.cancelFn(reason) : props.resolve(false)
}
const onInputChanged = (event) => {
@ -142,6 +142,8 @@ const ModalWrapper = (props: ModalWrapperProps) => {
props.handleHide()
}
if (!props.id || props.id === '') return null
return <ModalDialog id={props.id} {...state} handleHide={handleHide} />
}
export default ModalWrapper

@ -5,7 +5,7 @@ import { AppAction } from '../actions/app'
export type appProviderContextType = {
settings: any,
showMatamo: boolean,
showMatomo: boolean,
showEnter: boolean,
appManager: any
modal: any

@ -23,7 +23,8 @@ export const ModalProvider = ({ children = [], reducer = modalReducer, initialSt
}
const modal = (modalData: AppModal) => {
const { id, title, message, validationFn, okLabel, okFn, cancelLabel, cancelFn, modalType, modalParentClass, defaultValue, hideFn, data } = modalData
const { id, title, message, validationFn, okLabel, okFn, cancelLabel, cancelFn, modalType, modalParentClass, defaultValue, hideFn, data, preventBlur } = modalData
console.log('modalData', modalData)
return new Promise((resolve, reject) => {
dispatch({
type: modalActionTypes.setModal,
@ -42,7 +43,8 @@ export const ModalProvider = ({ children = [], reducer = modalReducer, initialSt
hideFn,
resolve,
next: onNextFn,
data
data,
preventBlur
}
})
})

@ -1,5 +1,5 @@
import { GitHubUser } from '@remix-api'
import { ModalTypes } from '../types'
import { AppModalCancelTypes, ModalTypes } from '../types'
export type ValidationResult = {
valid: boolean,
@ -17,14 +17,15 @@ export interface AppModal {
okLabel: string | JSX.Element
okFn?: (value?:any) => void
cancelLabel?: string | JSX.Element
cancelFn?: () => void,
cancelFn?: (reason?: AppModalCancelTypes) => void,
modalType?: ModalTypes,
modalParentClass?: string
defaultValue?: string
hideFn?: () => void,
resolve?: (value?:any) => void,
next?: () => void,
data?: any
data?: any,
preventBlur?: boolean
}
export interface AlertModal {

@ -23,7 +23,8 @@ export const modalReducer = (state: ModalState = ModalInitialState, action: Moda
hideFn: action.payload.hideFn,
resolve: action.payload.resolve,
next: action.payload.next,
data: action.payload.data
data: action.payload.data,
preventBlur: action.payload.preventBlur
}
const modalList: AppModal[] = state.modals.slice()

@ -60,7 +60,7 @@ const RemixApp = (props: IRemixAppUi) => {
activateApp()
}
const hadUsageTypeAsked = localStorage.getItem('hadUsageTypeAsked')
if (props.app.showMatamo) {
if (props.app.showMatomo) {
// if matomo dialog is displayed, it will take care of calling "setShowEnterDialog",
// if the user approves matomo tracking.
// so "showEnterDialog" stays false
@ -149,7 +149,7 @@ const RemixApp = (props: IRemixAppUi) => {
const value: appProviderContextType = {
settings: props.app.settings,
showMatamo: props.app.showMatamo,
showMatomo: props.app.showMatomo,
appManager: props.app.appManager,
showEnter: props.app.showEnter,
modal: props.app.notification,

@ -8,6 +8,15 @@ export const enum ModalTypes {
forceChoice = 'forceChoice'
}
export const enum AppModalCancelTypes {
close = 'close',
cancel = 'cancel',
blur = 'blur',
escape = 'escape',
enter = 'enter',
click = 'click'
}
export const enum UsageTypes {
Beginner = 1,
Prototyper,

@ -2,6 +2,7 @@ import React, {useRef, useState, useEffect} from 'react' // eslint-disable-line
import {ModalDialogProps} from './types' // eslint-disable-line
import './remix-ui-modal-dialog.css'
import { AppModalCancelTypes } from '@remix-ui/app'
declare global {
// eslint-disable-next-line no-unused-vars
@ -24,12 +25,14 @@ export const ModalDialog = (props: ModalDialogProps) => {
}
useEffect(() => {
if (!props.id) return
calledHideFunctionOnce.current = props.hide
modal.current.focus()
if (modal.current) {
if (!props.hide) {
modal.current.focus()
modal.current.removeEventListener('blur', handleBlur)
modal.current.addEventListener('blur', handleBlur)
if (modal.current && !props.preventBlur) {
modal.current.addEventListener('blur', handleBlur)
}
}
return () => {
modal.current && modal.current.removeEventListener('blur', handleBlur)
@ -37,11 +40,11 @@ export const ModalDialog = (props: ModalDialogProps) => {
}, [props.hide])
function handleBlur(e) {
if (!e.currentTarget.contains(e.relatedTarget)) {
if (e.currentTarget && !e.currentTarget.contains(e.relatedTarget)) {
e.stopPropagation()
if (document.activeElement !== this) {
!window.testmode && handleHide()
!window.testmode && props.cancelFn && props.cancelFn()
!window.testmode && props.cancelFn && props.cancelFn(AppModalCancelTypes.blur)
}
}
}
@ -49,7 +52,7 @@ export const ModalDialog = (props: ModalDialogProps) => {
const modalKeyEvent = (keyCode) => {
if (keyCode === 27) {
// Esc
if (props.cancelFn) props.cancelFn()
if (props.cancelFn) props.cancelFn(AppModalCancelTypes.escape)
handleHide()
} else if (keyCode === 13) {
// Enter
@ -71,7 +74,7 @@ export const ModalDialog = (props: ModalDialogProps) => {
if (state.toggleBtn) {
if (props.okFn) props.okFn()
} else {
if (props.cancelFn) props.cancelFn()
if (props.cancelFn) props.cancelFn(AppModalCancelTypes.enter)
}
handleHide()
}
@ -99,7 +102,7 @@ export const ModalDialog = (props: ModalDialogProps) => {
{props.title && props.title}
</h6>
{!props.showCancelIcon && (
<span className="modal-close" onClick={() => handleHide()}>
<span data-id={`${props.id}-modal-close`} className="modal-close" onClick={() => handleHide()}>
<i className="fas fa-times" aria-hidden="true"></i>
</span>
)}
@ -130,7 +133,7 @@ export const ModalDialog = (props: ModalDialogProps) => {
className={'modal-cancel btn btn-sm ' + (props.cancelBtnClass ? props.cancelBtnClass : state.toggleBtn ? 'border-secondary' : 'border-primary')}
data-dismiss="modal"
onClick={() => {
if (props.cancelFn) props.cancelFn()
if (props.cancelFn) props.cancelFn(AppModalCancelTypes.click)
handleHide()
}}
>

@ -1,3 +1,5 @@
import { AppModalCancelTypes } from "@remix-ui/app"
export type ValidationResult = {
valid: boolean,
message?: string
@ -15,7 +17,7 @@ export interface ModalDialogProps {
okFn?: (value?:any) => void,
donotHideOnOkClick?: boolean,
cancelLabel?: string | JSX.Element,
cancelFn?: () => void,
cancelFn?: (reason?: AppModalCancelTypes) => void,
modalClass?: string,
modalParentClass?: string
showCancelIcon?: boolean,
@ -26,5 +28,6 @@ export interface ModalDialogProps {
next?: () => void,
data?: any,
okBtnClass?: string,
cancelBtnClass?: string
cancelBtnClass?: string,
preventBlur?: boolean
}

@ -315,7 +315,7 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
</div>
<div className="custom-control custom-checkbox mb-1">
<input onChange={onchangeMatomoAnalytics} id="settingsMatomoAnalytics" type="checkbox" className="custom-control-input" checked={isMatomoChecked} />
<label className={`form-check-label custom-control-label align-middle ${getTextClass('settings/matomo-analytics')}`} htmlFor="settingsMatomoAnalytics">
<label data-id="label-matomo-settings" className={`form-check-label custom-control-label align-middle ${getTextClass('settings/matomo-analytics')}`} htmlFor="settingsMatomoAnalytics">
<span>
<FormattedMessage id="settings.matomoAnalytics" />
</span>

@ -41,6 +41,7 @@ export const copilotTemperature = (config, checked, dispatch) => {
export const useMatomoAnalytics = (config, checked, dispatch) => {
config.set('settings/matomo-analytics', checked)
localStorage.setItem('matomo-analytics-consent', Date.now().toString())
dispatch({ type: 'useMatomoAnalytics', payload: { isChecked: checked, textClass: checked ? textDark : textSecondary } })
if (checked) {
// user has given consent to process their data

Loading…
Cancel
Save