Skip to content

Commit

Permalink
Init
Browse files Browse the repository at this point in the history
  • Loading branch information
zachrip committed Mar 12, 2023
0 parents commit 041b573
Show file tree
Hide file tree
Showing 45 changed files with 31,002 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/** @type {import('eslint').Linter.Config} */
module.exports = {
extends: ['@remix-run/eslint-config', '@remix-run/eslint-config/node'],
};
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
node_modules

/.cache
/build
/dist
/public/build
.env
user*.json
6 changes: 6 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"singleQuote": true,
"useTabs": true,
"tabWidth": 2
}
16 changes: 16 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"editor.comments.insertSpace": false,
"editor.formatOnSave": true,
"editor.tabSize": 2,
"files.insertFinalNewline": true,
"files.trimTrailingWhitespace": true,
"typescript.tsdk": "node_modules/typescript/lib",
"search.exclude": {
"**/node_modules": true,
"**/bower_components": true,
"**/*.code-search": true,
"**/lib": true,
"**/build": true,
"**/dist": true
}
}
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# ValPal

I've created this project to allow Valorant players to create loadouts which can be randomized when joining a game. It supports agent specific loadouts, player cards, player titles, sprays, buddies, skin levels, and skin chromas.

# Screenshots

Configurator main screen showing a loadout
<img src="images/main-loadout.png" />
Configurator main screen showing an agent specific loadout
<img src="images/agent-loadout.png" />

# Installation

1. You can find the latest release [here](https://github.com/zachrip/valpal/releases/latest) - download valpal.exe
2. Create a folder somewhere (desktop or your user directory, or wherever else, you do you)
3. Run the .exe - windows will probably complain, click "more info" then "run anyway"
4. You should see an item added to your system tray - right click on it and click "Open ValPal" - this will open your browser to the configurator
5. Open Valorant (you must run the game for this to work)
6. Add a loadout and configure it with your skins
7. When you join a match, the app will detect when you've locked in as an agent. If you have `Agent specific loadouts` enabled it will configure loadouts for that agent if there are any (otherwise it will fall back to all enabled loadouts). You can disable the loadout shuffling all together if you prefer to equip loadouts by hand in the configurator

# Architecture

This app is based around [Remix](https://remix.run) and packaged using [pkg](https://github.com/vercel/pkg). It uses the local websocket/http server that Valorant runs to authenticate and detect when matches start.

# Contribute

1. Clone this repo
2. `npm install`
3. `npm run dev`
4. Run Valorant
5. Open [http://localhost:3000](http://localhost:3000)
6. The configurator code mostly lives in app/ - the rest of the stuff is in server/ (such as the game detection, system tray stuff, etc)
26 changes: 26 additions & 0 deletions app/components/Gallery.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { ReactNode } from 'react';
import { useEffect, useState } from 'react';

export const Gallery = <T extends Array<{ duration: number }>>({
items,
render,
}: {
items: T;
render: (data: T[number]) => JSX.Element;
}) => {
const [selectedElement, setSelectedElement] = useState(0);

useEffect(() => {
if (items.length === 0) return;

const interval = setInterval(() => {
setSelectedElement((el) => (el + 1) % items.length);
}, 1500);

return () => clearInterval(interval);
}, [items]);

if (items.length === 0) return null;

return render(items[selectedElement]);
};
33 changes: 33 additions & 0 deletions app/components/SwitchImage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { FunctionComponent } from 'react';
import { useEffect, useState } from 'react';

export const SwitchImage: FunctionComponent<
{
images: Array<{
src: string;
alt: string;
}>;
} & React.DetailedHTMLProps<
React.ImgHTMLAttributes<HTMLImageElement>,
HTMLImageElement
>
> = ({ images, ...props }) => {
const [selectedImage, setSelectedImage] = useState(0);

useEffect(() => {
const interval = setInterval(() => {
setSelectedImage((selectedImage + 1) % images.length);
}, 1500);

return () => clearInterval(interval);
}, [selectedImage, images]);

const image = images[selectedImage];

if (!image) {
return null;
}

const { src, alt } = image;
return <img {...props} src={src} alt={alt} />;
};
22 changes: 22 additions & 0 deletions app/entry.client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { RemixBrowser } from '@remix-run/react';
import { startTransition, StrictMode } from 'react';
import { hydrateRoot } from 'react-dom/client';

function hydrate() {
startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<RemixBrowser />
</StrictMode>
);
});
}

if (typeof requestIdleCallback === 'function') {
requestIdleCallback(hydrate);
} else {
// Safari doesn't support requestIdleCallback
// https://caniuse.com/requestidlecallback
setTimeout(hydrate, 1);
}
111 changes: 111 additions & 0 deletions app/entry.server.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { PassThrough } from 'stream';
import type { EntryContext } from '@remix-run/node';
import { Response } from '@remix-run/node';
import { RemixServer } from '@remix-run/react';
import isbot from 'isbot';
import { renderToPipeableStream } from 'react-dom/server';

const ABORT_DELAY = 5000;

export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) {
return isbot(request.headers.get('user-agent'))
? handleBotRequest(
request,
responseStatusCode,
responseHeaders,
remixContext
)
: handleBrowserRequest(
request,
responseStatusCode,
responseHeaders,
remixContext
);
}

function handleBotRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) {
return new Promise((resolve, reject) => {
let didError = false;

const { pipe, abort } = renderToPipeableStream(
<RemixServer context={remixContext} url={request.url} />,
{
onAllReady() {
const body = new PassThrough();

responseHeaders.set('Content-Type', 'text/html');

resolve(
new Response(body, {
headers: responseHeaders,
status: didError ? 500 : responseStatusCode,
})
);

pipe(body);
},
onShellError(error: unknown) {
reject(error);
},
onError(error: unknown) {
didError = true;

console.error(error);
},
}
);

setTimeout(abort, ABORT_DELAY);
});
}

function handleBrowserRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) {
return new Promise((resolve, reject) => {
let didError = false;

const { pipe, abort } = renderToPipeableStream(
<RemixServer context={remixContext} url={request.url} />,
{
onShellReady() {
const body = new PassThrough();

responseHeaders.set('Content-Type', 'text/html');

resolve(
new Response(body, {
headers: responseHeaders,
status: didError ? 500 : responseStatusCode,
})
);

pipe(body);
},
onShellError(err: unknown) {
reject(err);
},
onError(error: unknown) {
didError = true;

console.error(error);
},
}
);

setTimeout(abort, ABORT_DELAY);
});
}
50 changes: 50 additions & 0 deletions app/root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { MetaFunction } from '@remix-run/node';
import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from '@remix-run/react';

import stylesheet from '~/tailwind.css';

export function links() {
return [
{ rel: 'stylesheet', href: stylesheet },
{ rel: 'preconnect', href: 'https://fonts.googleapis.com' },
{
rel: 'preconnect',
href: 'https://fonts.gstatic.com',
crossOrigin: 'true',
},
{
rel: 'stylesheet',
href: 'https://fonts.googleapis.com/css2?family=Readex+Pro:wght@200;300;400;500;600;700&display=swap',
},
];
}

export const meta: MetaFunction = () => ({
charset: 'utf-8',
title: 'ValPal',
viewport: 'width=device-width,initial-scale=1',
});

export default function App() {
return (
<html lang="en">
<head>
<Meta />
<Links />
</head>
<body className="bg-slate-800 text-white">
<Outlet />
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
}
19 changes: 19 additions & 0 deletions app/routes/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { redirect } from '@remix-run/node';
import { useLoaderData } from '@remix-run/react';
import { getUser } from '~/utils.server';

export async function loader() {
const user = await getUser();

if (!user) {
return redirect('/login');
}

return redirect('/loadouts');
}

export default function Index() {
const { userId } = useLoaderData<typeof loader>();

return <div>{userId}</div>;
}
Loading

0 comments on commit 041b573

Please sign in to comment.