Skip to content

Commit

Permalink
fix: hiddenClasses inline styles & number input sanitization
Browse files Browse the repository at this point in the history
  • Loading branch information
KraXen72 committed Jun 26, 2023
1 parent e102b31 commit eeea1a1
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 77 deletions.
54 changes: 20 additions & 34 deletions assets/settingCss.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* this css gets injected regardless of userPrefs, because it is required for settings to render correctly */
:root {
--crankshaft-gray: rgba(255,255,255,.6);
}
Expand All @@ -21,6 +22,23 @@
grid-template-areas: "icon title" "input input";
}

/* multiple select */
.crankshaft-multisel-parent {
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-auto-rows: 1fr;
gap: .25rem;

background: #232323;
border-radius: 10px;
margin-top: 0.8rem;
}
.crankshaft-multisel-parent label.hostOpt {
width: 100%;
margin: 0;
box-sizing: border-box;
}

.Crankshaft-settings .settName .setting-title {
grid-area: title;
}
Expand Down Expand Up @@ -197,6 +215,7 @@ input:checked+.advancedSlider:hover {
width: max-content;
z-index: 10;
}

/* settings refresh popup */
.refresh-popup {
height: min-content;
Expand Down Expand Up @@ -230,37 +249,4 @@ input:checked+.advancedSlider:hover {
border: 2px solid #333333
}

.crankshaft-multisel-parent {
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-auto-rows: 1fr;
gap: .25rem;

background: #232323;
border-radius: 10px;
margin-top: 0.8rem;
}
.crankshaft-multisel-parent label.hostOpt {
width: 100%;
margin: 0;
box-sizing: border-box;
}
/*
.crankshaft-multisel-parent label input {
visibility: hidden;
}
.crankshaft-multisel-parent label input::after {
content: "";
visibility: visible;
border: 2px solid rgba(128, 0, 0, 0.466);
display: block;
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
border-radius: 5px;
}
.crankshaft-multisel-parent label input:checked::after {
border-color: rgba(0, 128, 0, 0.466);
} */
.hiddenClasses-hideAds-bottomOffset { bottom: 80px !important; }
1 change: 0 additions & 1 deletion assets/splashCss.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* Removal - modified from https://envyxyz.github.io/removal/removal.css */
#loadEditrBtn,
#loadInfoLHolder,
#loadInfoRHolder {
Expand Down
14 changes: 5 additions & 9 deletions src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { join as pathJoin, resolve as pathResolve } from 'path';
import { ipcRenderer } from 'electron';
import { fetchGame } from './matchmaker';
import { createElement, hiddenClassesImages, injectSettingsCSS, toggleSettingCSS } from './utils';
import { hasOwn, createElement, hiddenClassesImages, injectSettingsCSS, toggleSettingCSS } from './utils';
import { renderSettings } from './settingsui';
import { compareVersions } from 'compare-versions';

Expand All @@ -20,7 +20,6 @@ export const strippedConsole = {
timeEnd: console.timeEnd.bind(console)
};

export const classPickerBottom = '80px';
const $assets = pathResolve(__dirname, '..', 'assets');
const repoID = 'KraXen72/crankshaft';
let lastActiveTab = 0;
Expand Down Expand Up @@ -73,18 +72,15 @@ ipcRenderer.on('checkForUpdates', async(event, currentVersion) => {
ipcRenderer.on('initDiscordRPC', () => {
// let areHiddenClassesHooked = false
function updateRPC() {
/** eslint correct Object.hasOwnProperty helper */
const has = (object: Object, key: string) => Object.prototype.hasOwnProperty.call(object, key);

strippedConsole.log('> updated RPC');
const classElem = document.getElementById('menuClassName');
const skinElem = document.querySelector('#menuClassSubtext > span');
const mapElem = document.getElementById('mapInfo');

const gameActivity = has(window, 'getGameActivity') ? window.getGameActivity() as Partial<GameInfo> : {};
const gameActivity = hasOwn(window, 'getGameActivity') ? window.getGameActivity() as Partial<GameInfo> : {};
let overWriteDetails: string | false = false;
if (!has(gameActivity, 'class')) gameActivity.class = { name: classElem === null ? '' : classElem.textContent };
if (!has(gameActivity, 'map') || !has(gameActivity, ('mode'))) overWriteDetails = (mapElem !== null) ? mapElem.textContent : 'Loading game...';
if (!hasOwn(gameActivity, 'class')) gameActivity.class = { name: classElem === null ? '' : classElem.textContent };
if (!hasOwn(gameActivity, 'map') || !hasOwn(gameActivity, ('mode'))) overWriteDetails = (mapElem !== null) ? mapElem.textContent : 'Loading game...';

const data: RPCargs = {
details: overWriteDetails || `${gameActivity.mode} on ${gameActivity.map}`,
Expand Down Expand Up @@ -167,7 +163,7 @@ ipcRenderer.on('injectClientCSS', (_event, _userPrefs: UserPrefs, version: strin

if (hideAds) {
toggleSettingCSS(styleSettingsCSS.hideAds, 'hideAds', true);
document.getElementById('hiddenClasses').style.bottom = classPickerBottom;
document.getElementById('hiddenClasses').classList.add("hiddenClasses-hideAds-bottomOffset")
}
if (menuTimer) toggleSettingCSS(styleSettingsCSS.menuTimer, 'menuTimer', true);
if (quickClassPicker) toggleSettingCSS(styleSettingsCSS.quickClassPicker, 'quickClassPicker', true);
Expand Down
30 changes: 22 additions & 8 deletions src/settingsui.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/* eslint-disable max-len */
import { writeFileSync } from 'fs';
import { ipcRenderer } from 'electron'; // add app if crashes
import { createElement, haveSameContents, toggleSettingCSS } from './utils';
import { styleSettingsCSS, classPickerBottom } from './preload';
import { classListSet, createElement, haveSameContents, toggleSettingCSS, hasOwn } from './utils';
import { styleSettingsCSS } from './preload';
import { su } from './userscripts';
import { MATCHMAKER_GAMEMODES, MATCHMAKER_REGIONS } from './matchmaker';

Expand Down Expand Up @@ -151,7 +151,7 @@ class SettingElem {

updateMethod: 'onchange' | 'oninput' | '';

updateKey: 'value' | 'checked' | '';
updateKey: 'value' | 'checked' | 'valueAsNumber' | '';

#wrapper: HTMLElement | false;

Expand Down Expand Up @@ -216,7 +216,7 @@ class SettingElem {
min="${props.min}" max="${props.max}" step="${props?.step ?? 1}"
/>
</span>`;
this.updateKey = 'value';
this.updateKey = 'valueAsNumber';
this.updateMethod = 'onchange';
break;
case 'heading':
Expand Down Expand Up @@ -260,9 +260,23 @@ class SettingElem {
if (this.updateKey === '') throw 'Invalid update key';
const target = elem.querySelector('.s-update') as HTMLInputElement;

const value = this.props.type === 'multisel'
? [...target.children].filter(child => child.querySelector('input:checked')).map(child => child.querySelector('.optName').textContent)
: target[this.updateKey];
// parse & sanitize the value from our input element
let dirtyValue: UserPrefs[keyof UserPrefs] = target[this.updateKey];
if (this.props.type === 'multisel') {
dirtyValue = [...target.children]
.filter(child => child.querySelector('input:checked'))
.map(child => child.querySelector('.optName').textContent);
}
if (typeof dirtyValue === 'number') {
const updateUI = () => { target.value = dirtyValue.toString() };
if (Number.isNaN(dirtyValue)) {
target.value = userPrefs[this.props.key].toString();
return; // revert UI and don't apply this change;
}
if (hasOwn(this.props, 'min') && dirtyValue < this.props.min) { dirtyValue = this.props.min; updateUI(); }
if (hasOwn(this.props, 'max') && dirtyValue > this.props.max) { dirtyValue = this.props.max; updateUI(); }
}
const value = dirtyValue; // so we don't accidentally mutate it later

if (callback === 'normal') {
ipcRenderer.send('logMainConsole', `recieved an update for ${this.props.key}: ${value}`);
Expand All @@ -273,7 +287,7 @@ class SettingElem {
if (typeof value === 'boolean') {
if (this.props.key === 'hideAds') {
toggleSettingCSS(styleSettingsCSS.hideAds, this.props.key, value);
document.getElementById('hiddenClasses').style.bottom = value ? classPickerBottom : null;
classListSet(document.getElementById('hiddenClasses'), value, 'hiddenClasses-hideAds-bottomOffset');
}
if (this.props.key === 'menuTimer') toggleSettingCSS(styleSettingsCSS.menuTimer, this.props.key, value);
if (this.props.key === 'quickClassPicker') toggleSettingCSS(styleSettingsCSS.quickClassPicker, this.props.key, value);
Expand Down
49 changes: 24 additions & 25 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,32 +98,20 @@ export function debounce(func: Function, timeout = 300) {
};
}

export function hiddenClassesImages(classNumberFallback: number) {
const wrapper = document.getElementById('hiddenClasses');
const prepend = (wrapper ? (wrapper.firstChild as HTMLElement).id : 'menuClassPicker0').slice(0, -1);
const count = wrapper ? wrapper.children.length : classNumberFallback;
const wrapperFull = wrapper !== null && wrapper.children.length > 0;

let css = '';

/*
* 810 is krunker's set middle element size
* for each gap (count - 1) we substract 4px for gap
* Math.min is a safety measure in case the buttons would be < 50px
*/
const buttonSize = Math.min(Math.round((810 - (4 * (count - 1))) / count), 50);
css += `#hiddenClasses [id^="menuClassPicker"] {
/** @param classesCount how many classes krunker currently has (custom-only) included */
export function hiddenClassesImages(classesCount: number) {
const prepend = 'menuClassPicker0'.slice(0, -1);

const gaps = (4 * (classesCount - 1)); // for each gap (classesCount - 1) we substract 4px
const theoreticalButtonSize = Math.round((810 - gaps) / classesCount); // 810 is krunker's hardcoded middle element width
const buttonSize = Math.min(theoreticalButtonSize, 50); // safety measure in case the buttons would be < 50px

let css = `#hiddenClasses [id^="menuClassPicker"] {
width: ${buttonSize}px; height: ${buttonSize}px;
background-size: ${buttonSize - 6}px ${buttonSize - 6}px;
}`;

if (wrapperFull) {
[...wrapper.children].forEach(child => {
css += `${child.id} { background-image: url("https://assets.krunker.io/textures/classes/icon_${child.id.replace(prepend, '')}.png"); } \n`;
});
} else { // the fallback is almost always used...
for (let i = 0; i < count; i++) css += `#${prepend}${i} { background-image: url("https://assets.krunker.io/textures/classes/icon_${i}.png"); } \n`;
}
}\n`;

for (let i = 0; i < classesCount; i++) css += `#${prepend}${i} { background-image: url("https://assets.krunker.io/textures/classes/icon_${i}.png"); } \n`;

return css;
}
Expand All @@ -139,7 +127,18 @@ export function secondsToTimestring(num: number) {
// https://www.30secondsofcode.org/js/s/arrays-have-same-contents/

// eslint-disable-next-line
export const haveSameContents = (array1: any[], array2: any[]) => {
export function haveSameContents(array1: any[], array2: any[]) {
for (const value of new Set([...array1, ...array2])) if (array1.filter(e => e === value).length !== array2.filter(e => e === value).length) return false;
return true;
};

/** add/remove class(es) to/from an element based on a boolean*/
export function classListSet(element: HTMLElement, value: boolean, ...classNames: string[]) {
if (value) {
element.classList.add(...classNames)
} else {
element.classList.remove(...classNames)
}
}

export const hasOwn = (object: Object, key: string) => Object.prototype.hasOwnProperty.call(object, key);

0 comments on commit eeea1a1

Please sign in to comment.