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. 22
      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. 18
      apps/remix-ide/src/assets/js/loader.js
  6. 2
      libs/remix-ui/app/src/index.ts
  7. 11
      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. 19
      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 '6fd22d6fe5549ad4c4d8fd3ca0b7816b.mod': 35 // remix desktop
} }
_paq.push(['trackEvent', 'App', 'load']);
this.matomoConfAlreadySet = Registry.getInstance().get('config').api.exists('settings/matomo-analytics') this.matomoConfAlreadySet = Registry.getInstance().get('config').api.exists('settings/matomo-analytics')
this.matomoCurrentSetting = Registry.getInstance().get('config').api.get('settings/matomo-analytics') this.matomoCurrentSetting = Registry.getInstance().get('config').api.get('settings/matomo-analytics')
let electronTracking = false let electronTracking = window.electronAPI ? await window.electronAPI.canTrackMatomo() : false
if (window.electronAPI) { const lastMatomoCheck = window.localStorage.getItem('matomo-analytics-consent')
electronTracking = await window.electronAPI.canTrackMatomo() 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.showMatamo = (matomoDomains[window.location.hostname] || electronTracking) && !this.matomoConfAlreadySet
this.walkthroughService = new WalkthroughService(appManager) this.walkthroughService = new WalkthroughService(appManager)

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

@ -109,6 +109,8 @@ module.exports = class SettingsTab extends ViewPlugin {
updateMatomoAnalyticsChoice(isChecked) { updateMatomoAnalyticsChoice(isChecked) {
this.config.set('settings/matomo-analytics', 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 this.useMatomoAnalytics = isChecked
if (!isChecked) { if (!isChecked) {
// revoke tracking consent // revoke tracking consent

@ -18,14 +18,26 @@ function trackDomain(domainToTrack) {
_paq.push(['trackPageView']); _paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']); _paq.push(['enableLinkTracking']);
_paq.push(['enableHeartBeatTimer']); _paq.push(['enableHeartBeatTimer']);
if (!window.localStorage.getItem('config-v0.8:.remix.config') || const remixConfig = 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'))) { if (!remixConfig || (remixConfig && !remixConfig.includes('settings/matomo-analytics'))) {
// require user tracking consent before processing data // require user tracking consent before processing data
_paq.push(['requireConsent']); _paq.push(['requireConsent']);
} else { } else {
try {
const config = JSON.parse(remixConfig);
if (config['settings/matomo-analytics'] === true) {
// user has given consent to process their data // user has given consent to process their data
_paq.push(['setConsentGiven']) _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 () { (function () {
var u = "https://ethereumfoundation.matomo.cloud/"; var u = "https://ethereumfoundation.matomo.cloud/";
_paq.push(['setTrackerUrl', u + 'matomo.php?debug=1']); _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 { ModalProvider, useDialogDispatchers } from './lib/remix-app/context/provider'
export { AppModal } from './lib/remix-app/interface/index' export { AppModal } from './lib/remix-app/interface/index'
export { AlertModal } 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' 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 { FormattedMessage } from 'react-intl'
import { AppContext } from '../../context/context' import { AppContext } from '../../context/context'
import { useDialogDispatchers } from '../../context/provider' import { useDialogDispatchers } from '../../context/provider'
import { AppModalCancelTypes } from '../../types'
declare global { declare global {
interface Window { interface Window {
_paq: any _paq: any
@ -15,7 +16,7 @@ interface MatomoDialogProps {
} }
const MatomoDialog = (props: MatomoDialogProps) => { const MatomoDialog = (props: MatomoDialogProps) => {
const { settings, showMatamo, appManager } = useContext(AppContext) const { settings, showMatomo, appManager } = useContext(AppContext)
const { modal } = useDialogDispatchers() const { modal } = useDialogDispatchers()
const [visible, setVisible] = useState<boolean>(props.hide) const [visible, setVisible] = useState<boolean>(props.hide)
@ -63,7 +64,7 @@ const MatomoDialog = (props: MatomoDialogProps) => {
} }
useEffect(() => { useEffect(() => {
if (visible && showMatamo) { if (visible && showMatomo) {
modal({ modal({
id: 'matomoModal', id: 'matomoModal',
title: <FormattedMessage id="remixApp.matomoTitle" />, title: <FormattedMessage id="remixApp.matomoTitle" />,
@ -72,18 +73,22 @@ const MatomoDialog = (props: MatomoDialogProps) => {
okFn: handleModalOkClick, okFn: handleModalOkClick,
cancelLabel: <FormattedMessage id="remixApp.decline" />, cancelLabel: <FormattedMessage id="remixApp.decline" />,
cancelFn: declineModal, cancelFn: declineModal,
preventBlur: true
}) })
} }
}, [visible]) }, [visible])
const declineModal = async () => { const declineModal = async (reason: AppModalCancelTypes) => {
if (reason === AppModalCancelTypes.click || reason === AppModalCancelTypes.enter) {
settings.updateMatomoAnalyticsChoice(false) settings.updateMatomoAnalyticsChoice(false)
// revoke tracking consent // revoke tracking consent
_paq.push(['forgetConsentGiven']) _paq.push(['forgetConsentGiven'])
setVisible(false) setVisible(false)
} }
}
const handleModalOkClick = async () => { const handleModalOkClick = async () => {
// user has given consent to process their data // user has given consent to process their data
_paq.push(['setConsentGiven']) _paq.push(['setConsentGiven'])
settings.updateMatomoAnalyticsChoice(true) settings.updateMatomoAnalyticsChoice(true)

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

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

@ -23,7 +23,8 @@ export const ModalProvider = ({ children = [], reducer = modalReducer, initialSt
} }
const modal = (modalData: AppModal) => { 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) => { return new Promise((resolve, reject) => {
dispatch({ dispatch({
type: modalActionTypes.setModal, type: modalActionTypes.setModal,
@ -42,7 +43,8 @@ export const ModalProvider = ({ children = [], reducer = modalReducer, initialSt
hideFn, hideFn,
resolve, resolve,
next: onNextFn, next: onNextFn,
data data,
preventBlur
} }
}) })
}) })

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

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

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

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

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

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

@ -315,7 +315,7 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
</div> </div>
<div className="custom-control custom-checkbox mb-1"> <div className="custom-control custom-checkbox mb-1">
<input onChange={onchangeMatomoAnalytics} id="settingsMatomoAnalytics" type="checkbox" className="custom-control-input" checked={isMatomoChecked} /> <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> <span>
<FormattedMessage id="settings.matomoAnalytics" /> <FormattedMessage id="settings.matomoAnalytics" />
</span> </span>

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

Loading…
Cancel
Save