initial
This commit is contained in:
commit
cf9cf06d2b
16 changed files with 670 additions and 0 deletions
175
.gitignore
vendored
Normal file
175
.gitignore
vendored
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
|
||||||
|
logs
|
||||||
|
_.log
|
||||||
|
npm-debug.log_
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Caches
|
||||||
|
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
|
||||||
|
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
|
||||||
|
pids
|
||||||
|
_.pid
|
||||||
|
_.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
|
||||||
|
.temp
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
|
||||||
|
# IntelliJ based IDEs
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Finder (MacOS) folder config
|
||||||
|
.DS_Store
|
8
.vscode/settings.json
vendored
Normal file
8
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"json.schemas": [
|
||||||
|
{
|
||||||
|
"fileMatch": ["manifest.json"],
|
||||||
|
"url": "https://json.schemastore.org/chrome-manifest"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
15
README.md
Normal file
15
README.md
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# tweaks
|
||||||
|
|
||||||
|
To install dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun install
|
||||||
|
```
|
||||||
|
|
||||||
|
To run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun run index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
This project was created using `bun init` in bun v1.1.20. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
|
17
biome.json
Normal file
17
biome.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
|
||||||
|
"organizeImports": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"formatter": {
|
||||||
|
"enabled": true,
|
||||||
|
"indentStyle": "space",
|
||||||
|
"indentWidth": 2
|
||||||
|
},
|
||||||
|
"linter": {
|
||||||
|
"enabled": true,
|
||||||
|
"rules": {
|
||||||
|
"recommended": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
BIN
bun.lockb
Normal file
BIN
bun.lockb
Normal file
Binary file not shown.
1
index.ts
Normal file
1
index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
console.log("Hello via Bun!");
|
11
manifest.json
Normal file
11
manifest.json
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"name": "OsuTweaks",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"manifest_version": 2,
|
||||||
|
"content_scripts": [
|
||||||
|
{
|
||||||
|
"matches": ["*://osu.ppy.sh/*"],
|
||||||
|
"js": ["src/script.ts"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
23
package.json
Normal file
23
package.json
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"name": "tweaks",
|
||||||
|
"module": "index.ts",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@biomejs/biome": "^1.8.3",
|
||||||
|
"@types/bun": "latest",
|
||||||
|
"@types/lodash.isequal": "^4.5.8",
|
||||||
|
"@types/webextension-polyfill": "^0.10.7",
|
||||||
|
"vite-plugin-web-extension": "^4.1.6"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"lodash.isequal": "^4.5.0",
|
||||||
|
"vite": "^5.3.5",
|
||||||
|
"webextension-polyfill": "^0.12.0"
|
||||||
|
}
|
||||||
|
}
|
170
src/colors.ts
Normal file
170
src/colors.ts
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
// From https://gist.githubusercontent.com/mjackson/5311256/raw/132d6d1f39bf422e03c8ab86b5329d6f4dfbc383/color-conversion-algorithms.js
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts an RGB color value to HSL. Conversion formula
|
||||||
|
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
|
||||||
|
* Assumes r, g, and b are contained in the set [0, 255] and
|
||||||
|
* returns h, s, and l in the set [0, 1].
|
||||||
|
*
|
||||||
|
* @param Number r The red color value
|
||||||
|
* @param Number g The green color value
|
||||||
|
* @param Number b The blue color value
|
||||||
|
* @return Array The HSL representation
|
||||||
|
*/
|
||||||
|
export function rgbToHsl(r: number, g: number, b: number) {
|
||||||
|
(r /= 255), (g /= 255), (b /= 255);
|
||||||
|
|
||||||
|
var max = Math.max(r, g, b),
|
||||||
|
min = Math.min(r, g, b);
|
||||||
|
var h,
|
||||||
|
s,
|
||||||
|
l = (max + min) / 2;
|
||||||
|
|
||||||
|
if (max == min) {
|
||||||
|
h = s = 0; // achromatic
|
||||||
|
} else {
|
||||||
|
var d = max - min;
|
||||||
|
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||||
|
|
||||||
|
switch (max) {
|
||||||
|
case r:
|
||||||
|
h = (g - b) / d + (g < b ? 6 : 0);
|
||||||
|
break;
|
||||||
|
case g:
|
||||||
|
h = (b - r) / d + 2;
|
||||||
|
break;
|
||||||
|
case b:
|
||||||
|
h = (r - g) / d + 4;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
h /= 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [h, s, l];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts an HSL color value to RGB. Conversion formula
|
||||||
|
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
|
||||||
|
* Assumes h, s, and l are contained in the set [0, 1] and
|
||||||
|
* returns r, g, and b in the set [0, 255].
|
||||||
|
*
|
||||||
|
* @param Number h The hue
|
||||||
|
* @param Number s The saturation
|
||||||
|
* @param Number l The lightness
|
||||||
|
* @return Array The RGB representation
|
||||||
|
*/
|
||||||
|
export function hslToRgb(h, s, l) {
|
||||||
|
var r, g, b;
|
||||||
|
|
||||||
|
if (s == 0) {
|
||||||
|
r = g = b = l; // achromatic
|
||||||
|
} else {
|
||||||
|
function hue2rgb(p, q, t) {
|
||||||
|
if (t < 0) t += 1;
|
||||||
|
if (t > 1) t -= 1;
|
||||||
|
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
||||||
|
if (t < 1 / 2) return q;
|
||||||
|
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||||
|
var p = 2 * l - q;
|
||||||
|
|
||||||
|
r = hue2rgb(p, q, h + 1 / 3);
|
||||||
|
g = hue2rgb(p, q, h);
|
||||||
|
b = hue2rgb(p, q, h - 1 / 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [r * 255, g * 255, b * 255];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts an RGB color value to HSV. Conversion formula
|
||||||
|
* adapted from http://en.wikipedia.org/wiki/HSV_color_space.
|
||||||
|
* Assumes r, g, and b are contained in the set [0, 255] and
|
||||||
|
* returns h, s, and v in the set [0, 1].
|
||||||
|
*
|
||||||
|
* @param Number r The red color value
|
||||||
|
* @param Number g The green color value
|
||||||
|
* @param Number b The blue color value
|
||||||
|
* @return Array The HSV representation
|
||||||
|
*/
|
||||||
|
export function rgbToHsv(r, g, b) {
|
||||||
|
(r /= 255), (g /= 255), (b /= 255);
|
||||||
|
|
||||||
|
var max = Math.max(r, g, b),
|
||||||
|
min = Math.min(r, g, b);
|
||||||
|
var h,
|
||||||
|
s,
|
||||||
|
v = max;
|
||||||
|
|
||||||
|
var d = max - min;
|
||||||
|
s = max == 0 ? 0 : d / max;
|
||||||
|
|
||||||
|
if (max == min) {
|
||||||
|
h = 0; // achromatic
|
||||||
|
} else {
|
||||||
|
switch (max) {
|
||||||
|
case r:
|
||||||
|
h = (g - b) / d + (g < b ? 6 : 0);
|
||||||
|
break;
|
||||||
|
case g:
|
||||||
|
h = (b - r) / d + 2;
|
||||||
|
break;
|
||||||
|
case b:
|
||||||
|
h = (r - g) / d + 4;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
h /= 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [h, s, v];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts an HSV color value to RGB. Conversion formula
|
||||||
|
* adapted from http://en.wikipedia.org/wiki/HSV_color_space.
|
||||||
|
* Assumes h, s, and v are contained in the set [0, 1] and
|
||||||
|
* returns r, g, and b in the set [0, 255].
|
||||||
|
*
|
||||||
|
* @param Number h The hue
|
||||||
|
* @param Number s The saturation
|
||||||
|
* @param Number v The value
|
||||||
|
* @return Array The RGB representation
|
||||||
|
*/
|
||||||
|
export function hsvToRgb(h, s, v) {
|
||||||
|
var r, g, b;
|
||||||
|
|
||||||
|
var i = Math.floor(h * 6);
|
||||||
|
var f = h * 6 - i;
|
||||||
|
var p = v * (1 - s);
|
||||||
|
var q = v * (1 - f * s);
|
||||||
|
var t = v * (1 - (1 - f) * s);
|
||||||
|
|
||||||
|
switch (i % 6) {
|
||||||
|
case 0:
|
||||||
|
(r = v), (g = t), (b = p);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
(r = q), (g = v), (b = p);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
(r = p), (g = v), (b = t);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
(r = p), (g = q), (b = v);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
(r = t), (g = p), (b = v);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
(r = v), (g = p), (b = q);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [r * 255, g * 255, b * 255];
|
||||||
|
}
|
63
src/dom.ts
Normal file
63
src/dom.ts
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
import isEqual from "lodash.isequal";
|
||||||
|
const REFRESH_RATE = 1000;
|
||||||
|
|
||||||
|
export async function waitForElementToExist(
|
||||||
|
selector: string,
|
||||||
|
): Promise<Element> {
|
||||||
|
let interval: Timer;
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
interval = setInterval(() => {
|
||||||
|
const thing = document.querySelector(selector);
|
||||||
|
if (thing) {
|
||||||
|
clearInterval(interval);
|
||||||
|
resolve(thing);
|
||||||
|
}
|
||||||
|
}, REFRESH_RATE);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDocumentReady(func: () => void) {
|
||||||
|
if (document.readyState === "loading") {
|
||||||
|
document.addEventListener("DOMContentLoaded", func);
|
||||||
|
} else {
|
||||||
|
func();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OsuPath {
|
||||||
|
pathname: string;
|
||||||
|
hash: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addPageChangeHandler(func: (path: OsuPath) => void) {
|
||||||
|
onDocumentReady(() => {
|
||||||
|
console.log("Page loaded.");
|
||||||
|
|
||||||
|
// detects which page we're on
|
||||||
|
// detect page change, ripped from osuplus
|
||||||
|
// this is really ugly but whatever
|
||||||
|
// spent an hour trying to jack some addEventListener calls with no success
|
||||||
|
|
||||||
|
let currentPath: OsuPath = {
|
||||||
|
pathname: location.pathname,
|
||||||
|
hash: location.hash,
|
||||||
|
};
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
const newPath = {
|
||||||
|
pathname: location.pathname,
|
||||||
|
hash: location.hash,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isEqual(currentPath, newPath)) {
|
||||||
|
func(newPath);
|
||||||
|
currentPath = newPath;
|
||||||
|
}
|
||||||
|
}, REFRESH_RATE);
|
||||||
|
|
||||||
|
// the very first time the browser is opened, handle the page visit
|
||||||
|
func(currentPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Isntallled event listener");
|
||||||
|
}
|
104
src/pages/beatmap.ts
Normal file
104
src/pages/beatmap.ts
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
import { hslToRgb, rgbToHsl } from "../colors";
|
||||||
|
import { waitForElementToExist, type OsuPath } from "../dom";
|
||||||
|
import { parseCssColor } from "../utils";
|
||||||
|
|
||||||
|
const MODE_HANDLER = /(?<mode>osu|taiko|fruits|mania)\/(?<bid>\d+)/;
|
||||||
|
|
||||||
|
export async function beatmapPageHandler(m: RegExpMatchArray, path: OsuPath) {
|
||||||
|
const m2 = (path.hash ?? "").match(MODE_HANDLER);
|
||||||
|
const modeString = m2[1];
|
||||||
|
|
||||||
|
// get the beatmapset data
|
||||||
|
// TODO: error checking?
|
||||||
|
const dataEl = document.getElementById("json-beatmapset");
|
||||||
|
const beatmapsetData = JSON.parse(dataEl?.textContent ?? "{}");
|
||||||
|
|
||||||
|
// remove existing difficulty name indicator
|
||||||
|
waitForElementToExist(".beatmapset-header__diff-name").then(
|
||||||
|
(diffNameIndicator) => {
|
||||||
|
diffNameIndicator.parentNode?.removeChild(diffNameIndicator);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// const starRatingIndicator = await waitForElementToExist(
|
||||||
|
// ".beatmapset-header__star-difficulty",
|
||||||
|
// );
|
||||||
|
|
||||||
|
// starRatingIndicator.parentNode.removeChild(starRatingIndicator);
|
||||||
|
|
||||||
|
// put the names into the bubbles
|
||||||
|
const beatmapMap = new Map<string, unknown>();
|
||||||
|
for (const beatmap of [
|
||||||
|
...beatmapsetData.beatmaps,
|
||||||
|
...beatmapsetData.converts,
|
||||||
|
])
|
||||||
|
beatmapMap.set(JSON.stringify([beatmap.id, beatmap.mode]), beatmap);
|
||||||
|
|
||||||
|
const beatmapPicker = await waitForElementToExist(
|
||||||
|
".beatmapset-beatmap-picker",
|
||||||
|
);
|
||||||
|
const beatmapPickerChildren: HTMLAnchorElement[] = Array.from(
|
||||||
|
beatmapPicker.children,
|
||||||
|
);
|
||||||
|
for (const child of beatmapPickerChildren) {
|
||||||
|
if (!child.href) continue;
|
||||||
|
|
||||||
|
const targetBeatmapId = Number.parseInt(child.href.split("/").at(-1));
|
||||||
|
const correspondingBeatmap = beatmapMap.get(
|
||||||
|
JSON.stringify([targetBeatmapId, modeString]),
|
||||||
|
);
|
||||||
|
|
||||||
|
// create the elements and add it to screen
|
||||||
|
const icon = child.children[0];
|
||||||
|
|
||||||
|
const container = document.createElement("div");
|
||||||
|
container.style.display = "flex";
|
||||||
|
container.style.flexDirection = "column";
|
||||||
|
container.style.gap = "0";
|
||||||
|
container.style.transform = "translateZ(1px)";
|
||||||
|
|
||||||
|
const diffNameContainer = document.createElement("div");
|
||||||
|
const diffNameNode = document.createTextNode(correspondingBeatmap.version);
|
||||||
|
diffNameContainer.appendChild(diffNameNode);
|
||||||
|
const colorString = icon.style.getPropertyValue("--diff");
|
||||||
|
const color = parseCssColor(colorString);
|
||||||
|
const hsl = rgbToHsl(...color);
|
||||||
|
// make sure the colors aren't too dim
|
||||||
|
if (hsl[2] < 0.5) hsl[2] = 0.5;
|
||||||
|
hsl[2] *= 1.5;
|
||||||
|
if (hsl[2] > 1) hsl[2] = 1;
|
||||||
|
let [r, g, b] = hslToRgb(...hsl);
|
||||||
|
diffNameContainer.style.lineHeight = "0.75";
|
||||||
|
child.style.color = `rgb(${r}, ${g}, ${b})`;
|
||||||
|
diffNameContainer.style.textShadow = "0 1px 3px rgba(0,0,0,.75)";
|
||||||
|
container.appendChild(diffNameContainer);
|
||||||
|
|
||||||
|
const starRating = correspondingBeatmap.difficulty_rating.toLocaleString(
|
||||||
|
"en-US",
|
||||||
|
{
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
maximumFractionDigits: 2,
|
||||||
|
useGrouping: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const infoContainer = document.createElement("small");
|
||||||
|
const infoNode = document.createTextNode(`${starRating}★`);
|
||||||
|
infoContainer.style.color = `rgb(${r}, ${g}, ${b})`;
|
||||||
|
infoContainer.style.textShadow = "0 1px 3px rgba(0,0,0,.75)";
|
||||||
|
infoContainer.style.display = "block";
|
||||||
|
infoContainer.style.fontSize = "75%";
|
||||||
|
infoContainer.appendChild(infoNode);
|
||||||
|
|
||||||
|
child.style.opacity = "1";
|
||||||
|
child.style.width = "auto";
|
||||||
|
child.style.height = "auto";
|
||||||
|
child.style.display = "flex";
|
||||||
|
child.style.alignItems = "center";
|
||||||
|
child.style.paddingLeft = "8px";
|
||||||
|
child.style.paddingRight = "12px";
|
||||||
|
child.style.gap = "6px";
|
||||||
|
container.appendChild(infoContainer);
|
||||||
|
|
||||||
|
child.replaceChildren(icon, container);
|
||||||
|
}
|
||||||
|
}
|
8
src/pages/user.ts
Normal file
8
src/pages/user.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
export async function userPageHandler(m, path) {
|
||||||
|
console.log("User page");
|
||||||
|
let currentUser = await wait(
|
||||||
|
() => unsafeWindow.currentUser,
|
||||||
|
(c) => !!c,
|
||||||
|
);
|
||||||
|
console.log(currentUser);
|
||||||
|
}
|
27
src/script.ts
Normal file
27
src/script.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { addPageChangeHandler, type OsuPath } from "./dom";
|
||||||
|
import { beatmapPageHandler } from "./pages/beatmap";
|
||||||
|
import { userPageHandler } from "./pages/user";
|
||||||
|
|
||||||
|
console.log("OsuTweaks Extension");
|
||||||
|
|
||||||
|
const pathHandlers: [
|
||||||
|
RegExp,
|
||||||
|
(m: RegExpMatchArray, _: OsuPath) => Promise<void>,
|
||||||
|
][] = [
|
||||||
|
[/\/users\/(\d+)(?<mode>\/(osu|taiko|fruits|mania).+)?/, userPageHandler],
|
||||||
|
[/\/beatmapsets\/(?<id>\d+).?/, beatmapPageHandler],
|
||||||
|
];
|
||||||
|
|
||||||
|
addPageChangeHandler((path) => {
|
||||||
|
console.log("Changed to page", path);
|
||||||
|
|
||||||
|
const { pathname } = path;
|
||||||
|
for (const [regex, callback] of pathHandlers) {
|
||||||
|
const m = pathname.match(regex);
|
||||||
|
if (m !== null) {
|
||||||
|
// a match was found
|
||||||
|
callback(m, path);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
12
src/utils.ts
Normal file
12
src/utils.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
// Color parsing function
|
||||||
|
// minified, original is from https://stackoverflow.com/a/68580275
|
||||||
|
export function parseCssColor(str: string): [number, number, number] {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
document.body.appendChild(div);
|
||||||
|
div.style.color = str;
|
||||||
|
const res = getComputedStyle(div)
|
||||||
|
.color.match(/[\.\d]+/g)
|
||||||
|
.map(Number);
|
||||||
|
div.remove();
|
||||||
|
return res;
|
||||||
|
}
|
27
tsconfig.json
Normal file
27
tsconfig.json
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
// Enable latest features
|
||||||
|
"lib": ["ESNext", "DOM"],
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"allowJs": true,
|
||||||
|
|
||||||
|
// Bundler mode
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
// Best practices
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
|
||||||
|
// Some stricter flags (disabled by default)
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"noPropertyAccessFromIndexSignature": false
|
||||||
|
}
|
||||||
|
}
|
9
vite.config.ts
Normal file
9
vite.config.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
import webExtension from "vite-plugin-web-extension";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [webExtension({
|
||||||
|
disableAutoLaunch: true,
|
||||||
|
browser: process.env.TARGET ?? "firefox"
|
||||||
|
})],
|
||||||
|
});
|
Loading…
Reference in a new issue