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