diff --git a/src/content/components/button.js b/src/content/components/button.js new file mode 100644 index 00000000..2f621d76 --- /dev/null +++ b/src/content/components/button.js @@ -0,0 +1,22 @@ +/** @jsx h */ +import { h } from 'dom-chef' + +export default ({ text, style = {}, ...props }) => ( + +) diff --git a/src/content/components/hr.js b/src/content/components/hr.js index 7c2b200b..cf67867d 100644 --- a/src/content/components/hr.js +++ b/src/content/components/hr.js @@ -1,4 +1,6 @@ /** @jsx h */ import { h } from 'dom-chef' -export default () =>
+export default () => ( +
+) diff --git a/src/content/components/key-stat.js b/src/content/components/key-stat.js index 9020b3f0..ff84be0d 100644 --- a/src/content/components/key-stat.js +++ b/src/content/components/key-stat.js @@ -2,8 +2,18 @@ import { h } from 'dom-chef' export default ({ key, stat }) => ( -
-
{stat}
- {key} +
+
{stat}
+
{key}
) diff --git a/src/content/components/section-title.js b/src/content/components/section-title.js index a31e861c..134971a8 100644 --- a/src/content/components/section-title.js +++ b/src/content/components/section-title.js @@ -2,5 +2,13 @@ import { h } from 'dom-chef' export default ({ title }) => ( -

{title}

+
+ {title} +
) diff --git a/src/content/features/add-player-profile-download-demo.js b/src/content/features/add-player-profile-download-demo.js deleted file mode 100644 index 3175fe76..00000000 --- a/src/content/features/add-player-profile-download-demo.js +++ /dev/null @@ -1,96 +0,0 @@ -/** @jsx h */ -import { h } from 'dom-chef' -import select from 'select-dom' -import styleInject from 'style-inject' -import get from 'lodash/get' -import { - hasFeatureAttribute, - setFeatureAttribute -} from '../helpers/dom-element' -import { getQuickMatch, getMatch, getPlayer } from '../helpers/faceit-api' -import getMatchHistory from '../helpers/match-history' -import { getPlayerProfileNickname } from '../helpers/player-profile' - -const FEATURE_ATTRIBUTE = 'demo-download' - -export default async parentElement => { - const matchHistoryElement = select( - 'div.js-match-history-stats', - parentElement - ) - - if (!matchHistoryElement) { - return - } - - if (!hasFeatureAttribute(FEATURE_ATTRIBUTE, matchHistoryElement)) { - styleInject(` - .match-history-stats__row th:nth-last-child(3) .entry { - text-align: right; - } - .match-history-stats__row td:nth-last-child(3) .entry { - text-align: right; - } - .match-history-stats__row td:nth-last-child(3) .entry img { - padding-left: 8px; - } - `) - setFeatureAttribute(FEATURE_ATTRIBUTE, matchHistoryElement) - } - - const matchElements = select.all( - 'tbody > tr.match-history-stats__row', - matchHistoryElement - ) - - if (matchElements.length === 0) { - return - } - - const nickname = getPlayerProfileNickname() - const player = await getPlayer(nickname) - - const matchHistory = await getMatchHistory(player.guid, matchElements.length) - - const matchElementsHead = select('thead > tr', matchHistoryElement) - - if (!hasFeatureAttribute(FEATURE_ATTRIBUTE, matchElementsHead)) { - matchElementsHead.append() - setFeatureAttribute(FEATURE_ATTRIBUTE, matchElementsHead) - } - - matchElements.forEach(async (matchElement, index) => { - if (hasFeatureAttribute(FEATURE_ATTRIBUTE, matchElement)) { - return - } - setFeatureAttribute(FEATURE_ATTRIBUTE, matchElement) - - const matchId = matchHistory[index].matchId - - const downloadButtonElement = ( - - { - e.stopPropagation() - const match = - (await getQuickMatch(matchId)) || (await getMatch(matchId)) - const demoUrl = - get(match, 'externalMatches[0].stats.demoFileUrl') || - match.demoUrl - if (demoUrl) { - window.open(demoUrl) - } - }} - > - Watch Demo - - - ) - - matchElement.insertBefore( - downloadButtonElement, - matchElement.children[matchElement.children.length - 1] - ) - }) -} diff --git a/src/content/features/add-player-profile-extended-stats.js b/src/content/features/add-player-profile-extended-stats.js index 705cc431..c28aaea6 100644 --- a/src/content/features/add-player-profile-extended-stats.js +++ b/src/content/features/add-player-profile-extended-stats.js @@ -17,15 +17,28 @@ import createHrElement from '../components/hr' const FEATURE_ATTRIBUTE = 'extended-stats' export default async parentElement => { - const profileElement = select('section.profile > div.profile', parentElement) + const playerProfileParasiteElement = select( + 'parasite-player-profile-content', + parentElement + ) + + if (!playerProfileParasiteElement) { + return + } + + const playerProfileElement = select( + '.sc-egCXko', + playerProfileParasiteElement.shadowRoot + ) if ( - !profileElement || - hasFeatureAttribute(FEATURE_ATTRIBUTE, profileElement) + !playerProfileElement || + playerProfileElement.children.length < 10 || + hasFeatureAttribute(FEATURE_ATTRIBUTE, playerProfileElement) ) { return } - setFeatureAttribute(FEATURE_ATTRIBUTE, profileElement) + setFeatureAttribute(FEATURE_ATTRIBUTE, playerProfileElement) const nickname = getPlayerProfileNickname() const game = getPlayerProfileStatsGame() @@ -47,67 +60,50 @@ export default async parentElement => { } = playerStats const statsElement = ( -
-
-
+
+
+
{createSectionTitleElement({ title: 'Last 20 Matches Statistics' })} -
-
- {createKeyStatElement({ - key: 'Average Kills', - stat: averageKills - })} -
-
- {createKeyStatElement({ - key: 'Average Headshots %', - stat: averageHeadshots - })} -
-
- {createKeyStatElement({ - key: 'Average K/D', - stat: averageKDRatio - })} -
-
- {createKeyStatElement({ - key: 'Average K/R', - stat: averageKRRatio - })} -
+
+ {createKeyStatElement({ + key: 'Average Kills', + stat: averageKills + })} + {createKeyStatElement({ + key: 'Average Headshots %', + stat: averageHeadshots + })} + {createKeyStatElement({ + key: 'Average K/D', + stat: averageKDRatio + })} + {createKeyStatElement({ + key: 'Average K/R', + stat: averageKRRatio + })}
-
+
{createSectionTitleElement({ title: 'Other Statistics' })} -
-
- {createKeyStatElement({ - key: 'AFK Times', - stat: afk - })} -
-
- {createKeyStatElement({ - key: 'Leave Times', - stat: leaver - })} -
+
+ {createKeyStatElement({ + key: 'AFK Times', + stat: afk + })} + {createKeyStatElement({ + key: 'Leave Times', + stat: leaver + })}
-
+
) - const mainStatisticsElements = select( - 'h2[translate-once="MAIN-STATISTICS"]', - parentElement - ).parentElement - - profileElement.insertBefore(statsElement, mainStatisticsElements.nextSibling) + const gamePreferencesElement = select('.sc-fCjnCc', playerProfileElement) - const HrElement = createHrElement() + playerProfileElement.insertBefore(statsElement, gamePreferencesElement) - profileElement.insertBefore(HrElement, statsElement) + playerProfileElement.insertBefore(createHrElement(), gamePreferencesElement) } diff --git a/src/content/features/add-player-profile-level-progress.js b/src/content/features/add-player-profile-level-progress.js index c24aa90d..39dc0d96 100644 --- a/src/content/features/add-player-profile-level-progress.js +++ b/src/content/features/add-player-profile-level-progress.js @@ -11,24 +11,20 @@ import { getPlayerProfileNickname, getPlayerProfileStatsGame } from '../helpers/player-profile' -import createSkillLevelElement from '../components/skill-level' +import createSkillLevelIconElement from '../components/skill-level' +import createHrElement from '../components/hr' +import createKeyStatElement from '../components/key-stat' +import createSectionTitleElement from '../components/section-title' const FEATURE_ATTRIBUTE = 'level-progress' -const keyStatElement = ({ key, stat }) => ( -
-
{stat}
- {key} -
-) - -const eloRangeElement = ({ from, to, style }) => ( -
+const createEloRangeElement = ({ from, to, style }) => ( +
{from} – {to}
) -const skillLevelElement = ({ +const createSkillLevelElement = ({ level, currentLevel, eloFrom, @@ -47,22 +43,35 @@ const skillLevelElement = ({ padding: '10px 0' }} > - {createSkillLevelElement({ level, style })} - {eloRangeElement({ from: eloFrom, to: eloTo, style })} + {createSkillLevelIconElement({ level, style })} + {createEloRangeElement({ from: eloFrom, to: eloTo, style })}
) } export default async parentElement => { - const profileElement = select('section.profile > div.profile', parentElement) + const playerProfileParasiteElement = select( + 'parasite-player-profile-content', + parentElement + ) + + if (!playerProfileParasiteElement) { + return + } + + const playerProfileElement = select( + '#__next > div', + playerProfileParasiteElement.shadowRoot + ) if ( - !profileElement || - hasFeatureAttribute(FEATURE_ATTRIBUTE, profileElement) + !playerProfileElement || + playerProfileElement.children.length < 10 || + hasFeatureAttribute(FEATURE_ATTRIBUTE, playerProfileElement) ) { return } - setFeatureAttribute(FEATURE_ATTRIBUTE, profileElement) + setFeatureAttribute(FEATURE_ATTRIBUTE, playerProfileElement) const nickname = getPlayerProfileNickname() const player = await getPlayer(nickname) @@ -77,119 +86,114 @@ export default async parentElement => { const progressWidth = (faceitElo / 2000) * 100 const levelProgressElement = ( -
-

Level Progress

-
-
- {keyStatElement({ - key: 'Level', - stat: createSkillLevelElement({ level: currentLevel }) - })} -
-
- {keyStatElement({ key: 'Elo', stat: faceitElo })} -
-
- {currentLevel === 10 - ? keyStatElement({ key: `Maximum level reached`, stat: '🔥' }) - : keyStatElement({ - key: `Points needed to reach level ${currentLevel + 1}`, - stat: LEVELS[currentLevel + 1][0] - faceitElo - })} -
+
+ {createSectionTitleElement({ title: 'Level Progress' })} +
+ {createKeyStatElement({ + key: 'Level', + stat: createSkillLevelIconElement({ level: currentLevel }) + })} + {createKeyStatElement({ key: 'Elo', stat: faceitElo })} + {currentLevel === 10 + ? createKeyStatElement({ key: `Maximum level reached`, stat: '🔥' }) + : createKeyStatElement({ + key: `Points needed to reach level ${currentLevel + 1}`, + stat: LEVELS[currentLevel + 1][0] - faceitElo + })}
-
-
-
-
-
100 ? '100%' : `${progressWidth}%`, - height: '100%', - background: '#f50' - }} - /> -
-
- {skillLevelElement({ - level: 1, - currentLevel, - eloFrom: 1, - eloTo: 800, - width: 40 - })} - {skillLevelElement({ - level: 2, - currentLevel, - eloFrom: 801, - eloTo: 950 - })} - {skillLevelElement({ - level: 3, - currentLevel, - eloFrom: 951, - eloTo: 1100 - })} - {skillLevelElement({ - level: 4, - currentLevel, - eloFrom: 1101, - eloTo: 1250 - })} - {skillLevelElement({ - level: 5, - currentLevel, - eloFrom: 1251, - eloTo: 1400 - })} - {skillLevelElement({ - level: 6, - currentLevel, - eloFrom: 1401, - eloTo: 1550 - })} - {skillLevelElement({ - level: 7, - currentLevel, - eloFrom: 1551, - eloTo: 1700 - })} - {skillLevelElement({ - level: 8, - currentLevel, - eloFrom: 1701, - eloTo: 1850 - })} - {skillLevelElement({ - level: 9, - currentLevel, - eloFrom: 1851, - eloTo: 2000 - })} -
+
+
+
+ {createSkillLevelElement({ + level: 1, + currentLevel, + eloFrom: 1, + eloTo: 800, + width: 40 + })} + {createSkillLevelElement({ + level: 2, + currentLevel, + eloFrom: 801, + eloTo: 950 + })} + {createSkillLevelElement({ + level: 3, + currentLevel, + eloFrom: 951, + eloTo: 1100 + })} + {createSkillLevelElement({ + level: 4, + currentLevel, + eloFrom: 1101, + eloTo: 1250 + })} + {createSkillLevelElement({ + level: 5, + currentLevel, + eloFrom: 1251, + eloTo: 1400 + })} + {createSkillLevelElement({ + level: 6, + currentLevel, + eloFrom: 1401, + eloTo: 1550 + })} + {createSkillLevelElement({ + level: 7, + currentLevel, + eloFrom: 1551, + eloTo: 1700 + })} + {createSkillLevelElement({ + level: 8, + currentLevel, + eloFrom: 1701, + eloTo: 1850 + })} + {createSkillLevelElement({ + level: 9, + currentLevel, + eloFrom: 1851, + eloTo: 2000 + })}
-
+
100 ? '#f50' : '#323737' + width: progressWidth > 100 ? '100%' : `${progressWidth}%`, + height: '100%', + background: '#f50' }} /> - {skillLevelElement({ - level: 10, - currentLevel, - eloFrom: 2001, - eloTo: '∞', - width: 100, - borderRight: false - })}
+
+ {createSkillLevelElement({ + level: 10, + currentLevel, + eloFrom: 2001, + eloTo: '∞', + width: 100, + borderRight: false + })} +
100 ? '#f50' : '#323737' + }} + /> +
-
+
) - profileElement.prepend(
) - profileElement.prepend(levelProgressElement) + const mainStatsElement = select('.sc-jpGZec', playerProfileElement) + + playerProfileElement.insertBefore(levelProgressElement, mainStatsElement) + + playerProfileElement.insertBefore(createHrElement(), mainStatsElement) } diff --git a/src/content/features/add-player-profile-matches-demo.js b/src/content/features/add-player-profile-matches-demo.js new file mode 100644 index 00000000..4c848c69 --- /dev/null +++ b/src/content/features/add-player-profile-matches-demo.js @@ -0,0 +1,112 @@ +/** @jsx h */ +import { h } from 'dom-chef' +import select from 'select-dom' +import get from 'lodash/get' +import { + hasFeatureAttribute, + setFeatureAttribute +} from '../helpers/dom-element' +import { + getQuickMatch, + getMatch, + getPlayer, + getPlayerMatches +} from '../helpers/faceit-api' +import { + getPlayerProfileNickname, + getPlayerProfileStatsGame +} from '../helpers/player-profile' +import createButton from '../components/button' + +const FEATURE_ATTRIBUTE = 'matches-demo' + +export default async parentElement => { + const playerProfileParasiteElement = select( + 'parasite-player-profile-content', + parentElement + ) + + if (!playerProfileParasiteElement) { + return + } + + const playerProfileElement = select( + '.sc-egCXko', + playerProfileParasiteElement.shadowRoot + ) + + const matchElements = select.all('.sc-dDxMOP', playerProfileElement) + + matchElements.shift() + + if ( + !playerProfileElement || + playerProfileElement.children.length < 10 || + matchElements.length === 0 || + hasFeatureAttribute(FEATURE_ATTRIBUTE, playerProfileElement) + ) { + return + } + setFeatureAttribute(FEATURE_ATTRIBUTE, playerProfileElement) + + const matchElementsHead = select('.sc-dDxMOP', playerProfileElement) + + matchElementsHead.append( + + Demo + + ) + + const nickname = getPlayerProfileNickname() + const player = await getPlayer(nickname) + const game = getPlayerProfileStatsGame() + const matches = await getPlayerMatches(player.guid, game) + + matchElements.forEach(async (matchElement, index) => { + const matchId = matches[index].matchId + + if (!matchId) { + return + } + + const downloadButtonElement = ( + + {createButton({ + text: 'Watch Demo', + onClick: async e => { + e.stopPropagation() + const match = + (await getQuickMatch(matchId)) || (await getMatch(matchId)) + const demoUrl = + get(match, 'externalMatches[0].stats.demoFileUrl') || + match.demoUrl || + match.demoUrLs[0] + + if (demoUrl) { + window.open(demoUrl) + } + } + })} + + ) + + matchElement.insertBefore( + downloadButtonElement, + matchElement.children[matchElement.children.length - 1] + ) + }) +} diff --git a/src/content/features/add-profile-matches-elo-points.js b/src/content/features/add-player-profile-matches-elo.js similarity index 72% rename from src/content/features/add-profile-matches-elo-points.js rename to src/content/features/add-player-profile-matches-elo.js index 9b901f1f..6ec580f8 100644 --- a/src/content/features/add-profile-matches-elo-points.js +++ b/src/content/features/add-player-profile-matches-elo.js @@ -13,30 +13,36 @@ import { } from '../helpers/dom-element' import { getIsFreeMember } from '../helpers/membership' -const FEATURE_ATTRIBUTE = 'elo-points' +const FEATURE_ATTRIBUTE = 'matches-elo' export default async parentElement => { - const matchHistoryElement = select( - 'div.js-match-history-stats', + const playerProfileParasiteElement = select( + 'parasite-player-profile-content', parentElement ) - const matchElements = select.all( - 'tbody > tr.match-history-stats__row', - matchHistoryElement + + if (!playerProfileParasiteElement) { + return + } + + const playerProfileElement = select( + '.sc-egCXko', + playerProfileParasiteElement.shadowRoot ) + const matchElements = select.all('.sc-dDxMOP', playerProfileElement) + + matchElements.shift() + if ( - !matchHistoryElement || + !playerProfileElement || + playerProfileElement.children.length < 10 || matchElements.length === 0 || - hasFeatureAttribute(FEATURE_ATTRIBUTE, matchHistoryElement) + hasFeatureAttribute(FEATURE_ATTRIBUTE, playerProfileElement) ) { return } - setFeatureAttribute(FEATURE_ATTRIBUTE, matchHistoryElement) - - if (matchElements.length === 0) { - return - } + setFeatureAttribute(FEATURE_ATTRIBUTE, playerProfileElement) const nickname = getPlayerProfileNickname() const game = getPlayerProfileStatsGame() @@ -86,7 +92,13 @@ export default async parentElement => { } const newEloElement = ( -
+
New Elo: {newElo}
) diff --git a/src/content/index.js b/src/content/index.js index 05008fba..2e99b7be 100755 --- a/src/content/index.js +++ b/src/content/index.js @@ -18,7 +18,7 @@ import clickMatchRoomConnectToServer from './features/click-match-room-connect-t import addHeaderLevelProgress from './features/add-header-level-progress' import hideMatchRoomPlayerControls from './features/hide-match-room-player-controls' import hideFaceitClientHasLandedBanner from './features/hide-faceit-client-has-landed-banner' -import addProfileMatchesEloPoints from './features/add-profile-matches-elo-points' +import addPlayerProfileMatchesElo from './features/add-player-profile-matches-elo' import clickMatchRoomVetoLocations from './features/click-match-room-veto-locations' import clickMatchRoomVetoMaps from './features/click-match-room-veto-maps' import clickModalMatchRoomCaptainOk from './features/click-modal-match-room-captain-ok' @@ -27,7 +27,7 @@ import addMatchRoomPickPlayerStats from './features/add-match-room-pick-player-s import addMatchRoomPickPlayerElos from './features/add-match-room-pick-player-elos' import addMatchRoomPickPlayerFlags from './features/add-match-room-pick-player-flags' import addPlayerControlsReportFix from './features/add-match-room-player-controls-report-fix' -import addPlayerProfileDownloadDemo from './features/add-player-profile-download-demo' +import addPlayerProfileMatchesDemo from './features/add-player-profile-matches-demo' import addPlayerProfileExtendedStats from './features/add-player-profile-extended-stats' import addPlayerProfileBadge from './features/add-player-profile-badge' import clickModalClose from './features/click-modal-close' @@ -48,8 +48,9 @@ function observeBody() { return } - const observer = new MutationObserver(() => { + const observer = new MutationObserver(mutationList => { const modalElement = select('.modal-dialog') + if (modalElement) { if (modals.isInviteToParty(modalElement)) { runFeatureIf( @@ -97,8 +98,8 @@ function observeBody() { addPlayerProfileLevelProgress, modalElement ) - addPlayerProfileDownloadDemo(modalElement) - addProfileMatchesEloPoints(modalElement) + addPlayerProfileMatchesDemo(modalElement) + addPlayerProfileMatchesElo(modalElement) addPlayerProfileExtendedStats(modalElement) } } @@ -171,8 +172,8 @@ function observeBody() { addPlayerProfileLevelProgress, mainContentElement ) - addProfileMatchesEloPoints(mainContentElement) - addPlayerProfileDownloadDemo(mainContentElement) + addPlayerProfileMatchesElo(mainContentElement) + addPlayerProfileMatchesDemo(mainContentElement) addPlayerProfileExtendedStats(mainContentElement) } } else if (pages.isTeamsOverview()) { @@ -183,6 +184,17 @@ function observeBody() { ) } } + + for (const mutation of mutationList) { + for (const addedNode of mutation.addedNodes) { + if (addedNode.shadowRoot) { + observer.observe(addedNode.shadowRoot, { + childList: true, + subtree: true + }) + } + } + } }) observer.observe(document.body, { childList: true, subtree: true })