diff --git a/README.md b/README.md index 8062617..6da2dd8 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,26 @@ Formally known as "Online Friends Count", this is a Replugged plug-in that has b - [ ] Add a settings page - [ ] Implement a context menu to quickly change settings +## Settings +To customize the counter, you will need to manually set your preferences by entering the following method under your DevTools console (`Ctrl` + `Shift` + `I` and click on the "Console" tab): + +```js +// Replace the `key` and `value` respectively - you can use the table below as a reference +(await replugged.settings.init('xyz.griefmodz.StatisticCounter')).set(key, value); +``` + +| Key | Description | Default | +| ------------------------ | ------------------------------ | ------- | +| `autoRotation` | Rotate between counters | `false` | +| `autoRotationDelay` | Delay between rotations | `3e4` | +| `autoRotationHoverPause` | Pause the rotation upon hover | `true` | +| `preserveLastCounter` | Remember the last counter | `false` | +| `online` | Enable online counter | `true` | +| `friends` | Enable friends counter | `true` | +| `pending` | Enable pending counter | `true` | +| `blocked` | Enable blocked counter | `true` | +| `guilds` | Enable servers counter | `true` | + ## Previews ### Counter diff --git a/manifest.json b/manifest.json index eb5f6e3..46ca8c2 100644 --- a/manifest.json +++ b/manifest.json @@ -7,12 +7,12 @@ "discordID": "350227339784880130", "github": "griefmodz" }, - "version": "1.0.0", + "version": "1.0.1", "updater": { "type": "github", "id": "griefmodz/statistic-counter" }, - "license": "OSL-3.0", + "license": "MIT", "type": "replugged-plugin", "renderer": "src/index.tsx" } diff --git a/package.json b/package.json index c44a4aa..4de5c38 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "statistic-counter", - "version": "1.0.0", + "version": "1.0.1", "description": "A Replugged plug-in that has been ported from Powercord which introduces an interchangeable statistic counter in-between the home button and servers list.", "engines": { "node": ">=14.0.0" @@ -20,10 +20,9 @@ }, "keywords": [], "author": "", - "license": "OSL-3.0", + "license": "MIT", "devDependencies": { "@electron/asar": "^3.2.1", - "@fal-works/esbuild-plugin-global-externals": "^2.1.2", "@types/node": "^18.11.2", "@typescript-eslint/eslint-plugin": "^5.40.1", "@typescript-eslint/parser": "^5.40.1", @@ -33,7 +32,7 @@ "eslint-plugin-node": "^11.1.0", "eslint-plugin-react": "^7.31.10", "prettier": "^2.8.1", - "replugged": "4.0.0-beta0.19", + "replugged": "4.0.0-beta0.20", "tsx": "^3.10.3", "typescript": "^4.8.4" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 18e8eba..fdacc7c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2,7 +2,6 @@ lockfileVersion: 5.4 specifiers: '@electron/asar': ^3.2.1 - '@fal-works/esbuild-plugin-global-externals': ^2.1.2 '@types/node': ^18.11.2 '@typescript-eslint/eslint-plugin': ^5.40.1 '@typescript-eslint/parser': ^5.40.1 @@ -12,13 +11,12 @@ specifiers: eslint-plugin-node: ^11.1.0 eslint-plugin-react: ^7.31.10 prettier: ^2.8.1 - replugged: 4.0.0-beta0.19 + replugged: 4.0.0-beta0.20 tsx: ^3.10.3 typescript: ^4.8.4 devDependencies: '@electron/asar': 3.2.1 - '@fal-works/esbuild-plugin-global-externals': 2.1.2 '@types/node': 18.11.2 '@typescript-eslint/eslint-plugin': 5.40.1_ukgdydjtebaxmxfqp5v5ulh64y '@typescript-eslint/parser': 5.40.1_z4bbprzjrhnsfa24uvmcbu7f5q @@ -28,7 +26,7 @@ devDependencies: eslint-plugin-node: 11.1.0_eslint@8.25.0 eslint-plugin-react: 7.31.10_eslint@8.25.0 prettier: 2.8.1 - replugged: 4.0.0-beta0.19 + replugged: 4.0.0-beta0.20 tsx: 3.10.4 typescript: 4.8.4 @@ -103,10 +101,6 @@ packages: - supports-color dev: true - /@fal-works/esbuild-plugin-global-externals/2.1.2: - resolution: {integrity: sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ==} - dev: true - /@humanwhocodes/config-array/0.10.7: resolution: {integrity: sha512-MDl6D6sBsaV452/QSdX+4CXIjZhIcI0PELsxUjk4U828yd58vk3bTIvk/6w5FY+4hIy9sLW0sfrV7K7Kc++j/w==} engines: {node: '>=10.10.0'} @@ -1524,8 +1518,8 @@ packages: engines: {node: '>=8'} dev: true - /replugged/4.0.0-beta0.19: - resolution: {integrity: sha512-OuOQt1QMTxe3mhhXyGrmbRc/Ipq56RAXIEA2mqsJFiZKCBFgR5erOWFLHygC9ZiXZnRnq8f76m3VeHFaREyIFA==} + /replugged/4.0.0-beta0.20: + resolution: {integrity: sha512-nPpRjCRSb6OtxMZ38Fbm2+yyHLb37m3EUgE5WizMsshDMECBgi0B9K1uNnoynBz10aNzELaTWcCPDXAErzC6pg==} engines: {node: '>=14.0.0'} dev: true diff --git a/scripts/build.ts b/scripts/build.ts index c46da82..8265923 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -1,149 +1,164 @@ -import esbuild from 'esbuild'; -import { globalExternals } from '@fal-works/esbuild-plugin-global-externals'; -import path, { join } from 'path'; -import fs, { existsSync, rmSync } from 'fs'; -import _manifest from '../manifest.json'; -import { Plugin } from 'replugged/dist/types/addon'; - -const manifest: Plugin = _manifest; - -const NODE_VERSION = '14'; -const CHROME_VERSION = '91'; - -const globalModules = { - replugged: { - varName: 'replugged', - namedExports: [ - 'Injector', - 'Logger', - 'webpack', - 'common', - 'notices', - 'commands', - 'settings', - 'quickCSS', - 'themes', - 'ignition', - 'plugins', - 'util', - 'types' - ], - defaultExport: true - } +import esbuild from "esbuild"; +import path, { join } from "path"; +import fs, { existsSync, rmSync } from "fs"; +import _manifest from "../manifest.json"; +import { PluginManifest } from "replugged/dist/types/addon"; + +const manifest: PluginManifest = _manifest; + +const NODE_VERSION = "14"; +const CHROME_VERSION = "91"; + +const globalModules: esbuild.Plugin = { + name: "globalModules", + setup: (build) => { + build.onResolve({ filter: /^replugged.+$/ }, (args) => { + if (args.kind !== "import-statement") return; + + return { + errors: [ + { + text: `Importing from a path (${args.path}) is not supported. Instead, please import from "replugged" and destructure the required modules.`, + }, + ], + }; + }); + + build.onResolve({ filter: /^replugged$/ }, (args) => { + if (args.kind !== "import-statement") return; + + return { + path: args.path, + namespace: "replugged", + }; + }); + + build.onLoad( + { + filter: /.*/, + namespace: "replugged", + }, + () => { + return { + contents: "module.exports = window.replugged", + }; + }, + ); + }, }; -const REPLUGGED_FOLDER_NAME = 'replugged'; +const REPLUGGED_FOLDER_NAME = "replugged"; export const CONFIG_PATH = (() => { switch (process.platform) { - case 'win32': - return join(process.env.APPDATA || '', REPLUGGED_FOLDER_NAME); - case 'darwin': - return join(process.env.HOME || '', 'Library', 'Application Support', REPLUGGED_FOLDER_NAME); + case "win32": + return join(process.env.APPDATA || "", REPLUGGED_FOLDER_NAME); + case "darwin": + return join(process.env.HOME || "", "Library", "Application Support", REPLUGGED_FOLDER_NAME); default: if (process.env.XDG_CONFIG_HOME) { return join(process.env.XDG_CONFIG_HOME, REPLUGGED_FOLDER_NAME); } - return join(process.env.HOME || '', '.config', REPLUGGED_FOLDER_NAME); + return join(process.env.HOME || "", ".config", REPLUGGED_FOLDER_NAME); } })(); const install: esbuild.Plugin = { - name: 'install', + name: "install", setup: (build) => { build.onEnd(() => { if (!process.env.NO_INSTALL) { - const dest = join(CONFIG_PATH, 'plugins', manifest.id); + const dest = join(CONFIG_PATH, "plugins", manifest.id); if (existsSync(dest)) { rmSync(dest, { recursive: true }); } - fs.cpSync('dist', dest, { recursive: true }); - console.log('Installed updated version'); + fs.cpSync("dist", dest, { recursive: true }); + console.log("Installed updated version"); } }); - } + }, }; -const watch = process.argv.includes('--watch'); +const watch = process.argv.includes("--watch"); const common: esbuild.BuildOptions = { - absWorkingDir: path.join(__dirname, '..'), + absWorkingDir: path.join(__dirname, ".."), bundle: true, minify: false, sourcemap: true, - format: 'cjs' as esbuild.Format, - logLevel: 'info', + format: "cjs" as esbuild.Format, + logLevel: "info", watch, - plugins: [install] + plugins: [install], }; const targets = []; -if ('renderer' in manifest) { +if ("renderer" in manifest) { targets.push( esbuild.build({ ...common, entryPoints: [manifest.renderer], - platform: 'browser', + platform: "browser", target: `chrome${CHROME_VERSION}`, - outfile: 'dist/renderer.js', - format: 'esm' as esbuild.Format, - plugins: [globalExternals(globalModules), install] - }) + outfile: "dist/renderer.js", + format: "esm" as esbuild.Format, + plugins: [globalModules, install], + }), ); - manifest.renderer = 'renderer.js'; + manifest.renderer = "renderer.js"; } -if ('preload' in manifest) { +if ("preload" in manifest) { targets.push( esbuild.build({ ...common, entryPoints: [manifest.preload], - platform: 'node', + platform: "node", target: [`node${NODE_VERSION}`, `chrome${CHROME_VERSION}`], - outfile: 'dist/preload.js', - external: ['electron'] - }) + outfile: "dist/preload.js", + external: ["electron"], + }), ); - manifest.preload = 'preload.js'; + manifest.preload = "preload.js"; } -if ('main' in manifest) { +if ("main" in manifest) { targets.push( esbuild.build({ ...common, entryPoints: [manifest.main], - platform: 'node', + platform: "node", target: `node${NODE_VERSION}`, - outfile: 'dist/main.js', - external: ['electron'] - }) + outfile: "dist/main.js", + external: ["electron"], + }), ); - manifest.main = 'main.js'; + manifest.main = "main.js"; } -if ('plaintextPatches' in manifest) { +if ("plaintextPatches" in manifest) { targets.push( esbuild.build({ ...common, entryPoints: [manifest.plaintextPatches], - platform: 'browser', + platform: "browser", target: `chrome${CHROME_VERSION}`, - outfile: 'dist/plaintextPatches.js', - format: 'esm' as esbuild.Format, - plugins: [globalExternals(globalModules), install] - }) + outfile: "dist/plaintextPatches.js", + format: "esm" as esbuild.Format, + plugins: [globalModules, install], + }), ); - manifest.plaintextPatches = 'plaintextPatches.js'; + manifest.plaintextPatches = "plaintextPatches.js"; } -if (!fs.existsSync('dist')) { - fs.mkdirSync('dist'); +if (!fs.existsSync("dist")) { + fs.mkdirSync("dist"); } -fs.writeFileSync('dist/manifest.json', JSON.stringify(manifest)); +fs.writeFileSync("dist/manifest.json", JSON.stringify(manifest)); Promise.all(targets); diff --git a/scripts/bundle.ts b/scripts/bundle.ts index fd909f5..ab7113b 100644 --- a/scripts/bundle.ts +++ b/scripts/bundle.ts @@ -1,8 +1,8 @@ -import asar from '@electron/asar'; -import { readFileSync } from 'fs'; -import { Plugin } from 'replugged/dist/types/addon'; +import asar from "@electron/asar"; +import { readFileSync } from "fs"; +import { PluginManifest } from "replugged/dist/types/addon"; -const manifest = JSON.parse(readFileSync('manifest.json', 'utf-8')) as Plugin; +const manifest = JSON.parse(readFileSync("manifest.json", "utf-8")) as PluginManifest; const outputName = `${manifest.id}.asar`; -asar.createPackage('dist', outputName); +asar.createPackage("dist", outputName); diff --git a/src/index.tsx b/src/index.tsx index 5281fea..4df5c85 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,5 +1,5 @@ -import { Injector, Logger, webpack } from 'replugged'; -import { findInReactTree } from './lib/util'; +import { Injector, Logger, util, webpack } from 'replugged'; +import { findInReactTree, forceUpdate } from './lib/util'; import { PLUGIN_ID } from './lib/constants'; import { Counter } from './components'; @@ -8,16 +8,22 @@ import type { GuildsNavComponent } from './types'; const inject = new Injector(); const logger = Logger.plugin(PLUGIN_ID.replace(/-/g, ' '), '#3ba55c'); -export function start(): void { +let GuildClasses: { guilds: string; }; + +export async function start(): Promise { + GuildClasses = await webpack.waitForModule(webpack.filters.byProps('guilds', 'sidebar')); + patchGuildsNav(); } export function stop(): void { inject.uninjectAll(); + + forceUpdate(document.querySelector(`.${GuildClasses.guilds}`)); } export async function patchGuildsNav(): Promise { - const t1 = performance.now(); + const start = performance.now(); const GuildsNav: GuildsNavComponent = await webpack.waitForModule(webpack.filters.bySource('guildsnav')); @@ -29,10 +35,17 @@ export async function patchGuildsNav(): Promise { const NavScroll = findInReactTree(res, (node) => node?.props?.onScroll); if (!NavScroll || !NavScroll.props?.children) return res; - const HomeButtonIndex = NavScroll.props.children.findIndex((child: React.ReactElement) => child?.type?.toString()?.includes('getHomeLink')); - const StatisticCounterIndex = HomeButtonIndex > -1 ? HomeButtonIndex + 1 : 2; + let StatisticCounterIndex = 2; - NavScroll.props.children.splice(StatisticCounterIndex, 0, ); + const FavouritesIndex = NavScroll.props.children.findIndex((child: React.ReactElement) => child?.type?.toString()?.includes('favorites')); + if (FavouritesIndex) { + StatisticCounterIndex = FavouritesIndex + 1; + } else { + const HomeButtonIndex = NavScroll.props.children.findIndex((child: React.ReactElement) => child?.type?.toString()?.includes('getHomeLink')); + HomeButtonIndex && (StatisticCounterIndex = HomeButtonIndex + 1); + } + + NavScroll.props.children.splice(StatisticCounterIndex, 0, ); return res; }); @@ -40,7 +53,9 @@ export async function patchGuildsNav(): Promise { return res; }); - const t2 = performance.now(); + forceUpdate(await util.waitFor(`.${GuildClasses.guilds}`)); + + const end = performance.now(); - logger.log(`“GuildsNav” patched, took ${t2 - t1} ms`); + logger.log(`“GuildsNav” patched, took ${end - start} ms`); } diff --git a/src/lib/util.ts b/src/lib/util.ts index 92bcf1a..4e62611 100644 --- a/src/lib/util.ts +++ b/src/lib/util.ts @@ -1,6 +1,10 @@ +import { Injector, util } from "replugged"; + +const inject = new Injector(); + export type Predicate = (arg: Arg) => boolean; -export const findInReactTree = (node: JSX.Element | JSX.Element[], predicate: Predicate): JSX.Element | null => { +export function findInReactTree(node: JSX.Element | JSX.Element[], predicate: Predicate): JSX.Element | null { const stack = [node].flat(); while (stack.length !== 0) { @@ -17,3 +21,18 @@ export const findInReactTree = (node: JSX.Element | JSX.Element[], predicate: Pr return null; }; + +export function forceUpdate(element: Element | null): void { + if (!element) return; + + const instance = util.getOwnerInstance(element); + if (instance) { + const forceRerender = inject.instead(instance, 'render', () => { + forceRerender(); + + return null; + }); + + instance.forceUpdate(() => instance.forceUpdate(() => {})); + } +}